在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. 【C#】泛型

    泛型是一个非常有用的技术,在博客园里面有太多说到泛型的好文章,这里我推荐一篇我个人觉得非常全面,也非常齐全的文章. (重造轮子很傻!!!) C# -- 泛型(1) C# -- 泛型(2) C# -- ...

  2. hello world! hello blog!

    2015年12月21日 16:42:15   博客开启!

  3. js常用的校验代码 (整理)

    /* 用途:检查输入手机号码是否正确 输入:str:字符串 返回:如果通过验证返回true,否则返回false */ function checkMobile(str){ var regu =/^[1 ...

  4. WinForm心得

    如果有UI部分可以复用,那么可以单独设置为一个UserControl或者一个自定义子类控件 如果是单一一个控件,只是继承并修改了默认控件的样式行为,那么可以直接创建一个class并继承该控件,缺点是D ...

  5. LightOJ 1094 - Farthest Nodes in a Tree

    http://lightoj.com/volume_showproblem.php?problem=1094 树的直径是指树的最长简单路. 求法: 两遍BFS :先任选一个起点BFS找到最长路的终点, ...

  6. git subrepo

    此文已由作者张磊授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 前言 目前对 git 仓库拆分的已有实现之一,并没有合并到 git 发行版中.项目的地址是 https://g ...

  7. 【Selenium专题】WebDriver启动Chrome浏览器(一)

    selenium操作chrome浏览器需要有ChromeDriver驱动来协助.一.什么是ChromeDriver?ChromeDriver是Chromium team开发维护的,它是实现WebDri ...

  8. 【Apache系列】linux下Apache的常用操作

    1. 启动/停止 1.1假设你的apahce安装目录为/usr/local/apache2,这些方法适合任何情况 apahce启动命令: /usr/local/apache2/bin/apachect ...

  9. [Swift]复数的表示和四则运算

    我们把形如z=a+bi(a,b均为实数)的数称为复数,其中a称为实部,b称为虚部,i称为虚数单位. 当虚部等于零时,这个复数可以视为实数:当z的虚部不等于零时,实部等于零时,常称z为纯虚数. 复数域是 ...

  10. 用IDA辅助分析grub的小技巧

    IDA可以辅助我们分析grub的代码,但是bootloader本身只是bin文件,为了让IDA能正确识别分析,我们还需要提供一些基本的参数,比如文件加载到内存的地址等. 图中MBR是加载到0x7C00 ...