在C语言中,如果我们要访问一个数组的某个下标对应的元素,通常的写法是a[i]。但从汇编的角度看,写成i[a]一点问题都没有。

下面通过代码给出证明。

o foo1.c

 int main(int argc, char *argv[])
{
unsigned int a[] = {, , };
unsigned int n = sizeof (a) / sizeof (int); unsigned int sum = ;
for (unsigned int i = ; i < n; i++)
sum += a[i]; return sum;
}

o foo2.c

 int main(int argc, char *argv[])
{
unsigned int a[] = {, , };
unsigned int n = sizeof (a) / sizeof (int); unsigned int sum = ;
for (unsigned int i = ; i < n; i++)
sum += i[a]; return sum;
}

o foo3.c

 int main(int argc, char *argv[])
{
unsigned int a[] = {, , };
unsigned int n = sizeof (a) / sizeof (int); unsigned int sum = ;
for (unsigned int i = ; i < n; i++)
sum += *(i+a); return sum;
}

o 编译和运行

 $ gcc -g -Wall -std=gnu99 -m32 -o foo1 foo1.c
$ gcc -g -Wall -std=gnu99 -m32 -o foo2 foo2.c
$ gcc -g -Wall -std=gnu99 -m32 -o foo3 foo3.c
$ ./foo1; echo $? $ ./foo2; echo $? $ ./foo3; echo $?

o 反汇编后diff

1) foo1.gdb.out

 (gdb) disas /m main
Dump of assembler code for function main:
{
0x080483ed <+>: push ebp
0x080483ee <+>: mov ebp,esp
0x080483f0 <+>: sub esp,0x20 unsigned int a[] = {, , };
0x080483f3 <+>: mov DWORD PTR [ebp-0xc],0x1
0x080483fa <+>: mov DWORD PTR [ebp-0x8],0x2
0x08048401 <+>: mov DWORD PTR [ebp-0x4],0x3 unsigned int n = sizeof (a) / sizeof (int);
0x08048408 <+>: mov DWORD PTR [ebp-0x10],0x3 unsigned int sum = ;
0x0804840f <+>: mov DWORD PTR [ebp-0x18],0x0 for (unsigned int i = ; i < n; i++)
0x08048416 <+>: mov DWORD PTR [ebp-0x14],0x0
0x0804841d <+>: jmp 0x804842d <main+>
0x08048429 <+>: add DWORD PTR [ebp-0x14],0x1
0x0804842d <+>: mov eax,DWORD PTR [ebp-0x14]
0x08048430 <+>: cmp eax,DWORD PTR [ebp-0x10]
0x08048433 <+>: jb 0x804841f <main+> sum += a[i];
0x0804841f <+>: mov eax,DWORD PTR [ebp-0x14]
0x08048422 <+>: mov eax,DWORD PTR [ebp+eax*-0xc]
0x08048426 <+>: add DWORD PTR [ebp-0x18],eax return sum;
0x08048435 <+>: mov eax,DWORD PTR [ebp-0x18] }
0x08048438 <+>: leave
0x08048439 <+>: ret End of assembler dump.

2) foo2.gdb.out

 (gdb) disas /m main
Dump of assembler code for function main:
{
0x080483ed <+>: push ebp
0x080483ee <+>: mov ebp,esp
0x080483f0 <+>: sub esp,0x20 unsigned int a[] = {, , };
0x080483f3 <+>: mov DWORD PTR [ebp-0xc],0x1
0x080483fa <+>: mov DWORD PTR [ebp-0x8],0x2
0x08048401 <+>: mov DWORD PTR [ebp-0x4],0x3 unsigned int n = sizeof (a) / sizeof (int);
0x08048408 <+>: mov DWORD PTR [ebp-0x10],0x3 unsigned int sum = ;
0x0804840f <+>: mov DWORD PTR [ebp-0x18],0x0 for (unsigned int i = ; i < n; i++)
0x08048416 <+>: mov DWORD PTR [ebp-0x14],0x0
0x0804841d <+>: jmp 0x804842d <main+>
0x08048429 <+>: add DWORD PTR [ebp-0x14],0x1
0x0804842d <+>: mov eax,DWORD PTR [ebp-0x14]
0x08048430 <+>: cmp eax,DWORD PTR [ebp-0x10]
0x08048433 <+>: jb 0x804841f <main+> sum += i[a];
0x0804841f <+>: mov eax,DWORD PTR [ebp-0x14]
0x08048422 <+>: mov eax,DWORD PTR [ebp+eax*-0xc]
0x08048426 <+>: add DWORD PTR [ebp-0x18],eax return sum;
0x08048435 <+>: mov eax,DWORD PTR [ebp-0x18] }
0x08048438 <+>: leave
0x08048439 <+>: ret End of assembler dump.

3) foo3.gdb.out

 (gdb) disas /m main
Dump of assembler code for function main:
{
0x080483ed <+>: push ebp
0x080483ee <+>: mov ebp,esp
0x080483f0 <+>: sub esp,0x20 unsigned int a[] = {, , };
0x080483f3 <+>: mov DWORD PTR [ebp-0xc],0x1
0x080483fa <+>: mov DWORD PTR [ebp-0x8],0x2
0x08048401 <+>: mov DWORD PTR [ebp-0x4],0x3 unsigned int n = sizeof (a) / sizeof (int);
0x08048408 <+>: mov DWORD PTR [ebp-0x10],0x3 unsigned int sum = ;
0x0804840f <+>: mov DWORD PTR [ebp-0x18],0x0 for (unsigned int i = ; i < n; i++)
0x08048416 <+>: mov DWORD PTR [ebp-0x14],0x0
0x0804841d <+>: jmp 0x8048437 <main+>
0x08048433 <+>: add DWORD PTR [ebp-0x14],0x1
0x08048437 <+>: mov eax,DWORD PTR [ebp-0x14]
0x0804843a <+>: cmp eax,DWORD PTR [ebp-0x10]
0x0804843d <+>: jb 0x804841f <main+> sum += *(i+a);
0x0804841f <+>: mov eax,DWORD PTR [ebp-0x14]
0x08048422 <+>: lea edx,[eax*+0x0]
0x08048429 <+>: lea eax,[ebp-0xc]
0x0804842c <+>: add eax,edx
0x0804842e <+>: mov eax,DWORD PTR [eax]
0x08048430 <+>: add DWORD PTR [ebp-0x18],eax return sum;
0x0804843f <+>: mov eax,DWORD PTR [ebp-0x18] }
0x08048442 <+>: leave
0x08048443 <+>: ret End of assembler dump.

4) a[i] v.s. i[a]

5) i[a] v.s. *(i+a)

结论: a[i]==i[a]==*(i+a)==*(a+i)

分析: 在编译器的眼里,数组名a不过是一段连续内存的首地址。获取某个元素a[i]不过是在a对应的首地址上做偏移,找到对应的内存地址后从中取出其中的内容即可。

PS: 我在面试别人的过程中,如果求职的工程师说他懂汇编,我一般会问这样的问题,"能否在C代码中使用i[a]去访问数组a的第i个元素?"。无论对方说能与不能,我都很乐意进一步问"为什么能/不能?"从而挖掘出其对汇编及编译过程的理解深度。 通常,优秀的程序员能回答得富有计算机思维(即使他判定为不能, 比如"我觉得不能,编译器应该不支持这种怪诞的用法..."),而那些机械的程序员一般会选择放弃思考为什么能/不能。

随机推荐

  1. Maven整理笔记の生命周期和插件

    项目构建的生命周期,其实软件开发人员每天都在干这个事,即项目清理.初始化.编译.测试.打包.集成测试.验证.部署和站点生成等,可以说几乎所有项目的构建都可以映射到这样一个生命周期上. Maven的插件 ...

  2. delphi7的adoconnection控件连接不上

    delphi时选择以{以管理员身份运行 }即可

  3. C#多线程学习(四) 多线程的自动管理(线程池)

    在多线程的程序中,经常会出现两种情况: 一种情况:   应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应                   这一般使用ThreadPo ...

  4. a标签点击时跳出确认框

    在做一些删除等的操作时,在跳转链接前,需要弹出一个确认框确认,避免误点. 方法一: <a  href="http://www.baidu.com" onClick=" ...

  5. yaml 配置

    yaml文件的作用 yaml是一种直观的能够被电脑识别的的数据序列化格式,容易被人类阅读,并且容易和脚本语言交互. yaml的语法规则 字母大小写敏感: 通过缩进来表示层级关系,同层级元素需左对齐,且 ...

  6. fread和fwrite用法小结

    fwrite和fread是以记录为单位的I/O函数,fread和fwrite函数一般用于二进制文件的输入输出. #include <stdio.h>size_t fread(void *p ...

  7. jquery源码解析:jQuery延迟对象Deferred(工具方法)详解1

    请先看上一课的回调对象.Deferred是通过extend添加到jQuery中的工具方法.如下所示: jQuery.extend({ Deferred: function( func ) { }, w ...

  8. Mysql创建、删除用户[转]

    MySql中添加用户,新建数据库,用户授权,删除用户,修改密码(注意每行后边都跟个;表示一个命令语句结束): 1.新建用户 登录MYSQL: @>mysql -u root -p @>密码 ...

  9. handlersocket优缺点

    HandlerSocket的优势和特点: 1)         支持多种查询模式 HandlerSocket目前支持索引查询(主键索引和非主键的普通索引均可),索引范围扫描,LIMIT子句,也即支持增 ...

  10. 队列优化dijsktra(SPFA)的玄学优化

    转载:大佬博客 最近想到了许多优化spfa的方法,这里想写个日报与大家探讨下 前置知识:spfa(不带任何优化) 由于使用较多 STLSTL ,本文中所有代码的评测均开启 O_2O2​ 优化 对一些数 ...