在c/c++语言中,为什么c[5] == 5[c]

博客转载请注明原地址: http://blog.csdn.net/sunliymonkey/article/details/48139183

问题:在c/c++语言中,为什么c[5] == 5[c]?

  这个问题,当初是在德问上看见的,起初自己也不知道其机理,猜测与c语言的编译机制有关,于是通过反汇编、猜测、验证,最终找到了原由。

下面是我分析该问题的过程,首先来看一段关于数组的代码:

#include<iostream>
using namespace std;
int main()
{
    int a[5];
    for(int i = 1; i < 5; i++)
    {
        a[i] = i + 5;
    }
    a[3] = 11;
    3[a] = 15;
    cout << a[3] << endl;
    system("pause");
    return 0;
}   

使用Microsoft Visual 2010对代码进行反汇编:

7:  {
8:      a[i] = i + 5;
00251B80 8B 45 D8      mov   eax,dword ptr [i]
00251B83 83 C0 05      add   eax,5              // eax = i + 5
00251B86 8B 4D D8      mov   ecx,dword ptr [i]  // 用ecx存放a[i]下标i的值
00251B89 89 44 8D E4   mov  dword ptr [ebp+ecx*4-1Ch],eax // 将eax的值传给a[i]
9: }

根据上面的汇编代码,我们可以发现编译器对于a[i]的汇编表示:

    a[i]:  dword ptr [ebp+ecx*4-1Ch]

  其中dword,表示双字(四个字节);ptr,pointer缩写,表示指针;[addr]中的addr表示地址位置。整个式子表示从位于ebp+ecx*4-1Ch的地址处,提取变量大小为4个字节的变量值,与a[i]进行对比,可以猜测:

    a[i]    <--->    [ebp+ecx*4-1Ch]
    a       <--->    ebp - 1Ch   //数组首地址
    i       <--->    ecx         //数组下标
    int     <--->    4           //变量大小

通过Microsoft Visual 2010对比aebp-1Ch的值,可以发现它们相等,也就证明了我们上面的对应关系,总结出编译器对于数组的解释:

    ptr [数组首地址 + 数组下标 * sizeof(变量类型)]

貌似这个结论,比较显然,但是如果按照这个结论来看:

  • a[3]翻译为: ptr [ a + 3 * sizeof(int) ]
  • 3[a]翻译为: ptr [ 3 + a * sizeof(int) ]

      如此来看,两者不应该是一样的,但是实际运行程序发现,两者确实相等。这让我们感觉到编译器非常聪明,能够识别出a才是真正的数组首地址,3才是真正的数组下标。考虑到编译器的才能是由我们程序员所赋予,显然其无法真正识别出谁是数组首地址,而是通过预设的指导规则得到。

      在这里,3a,对编译器来讲,前者是一个常数,后者是一个指针变量。从更高层次来看a[i][i]a,两者均为变量,不过其中一个是整数类型,另外一个是指针类型。能够猜测到,编译器应该是将指针变量识别为数组首地址,而剩余的变量值作为数组下标

接下来我们做以下实验进行验证:

    int a[5];
    int *p = a;
    int i = 2 , b = &a;
    //a: 数组首地址
    i[a] = 1;   // 正确
    2[a] = 1;   // 正确
    (i+2)[a+1]=1; // 正确

    //p: int指针变量
    i[p] = 1;   // 正确
    2[p] = 1;   // 正确
    (i+2)[p+1]=1; // 正确

    //无指针变量
    b[2] = 3;   // 失败 error C2109: 下标要求数组或指针类型
    2[b] = 4;   // 失败 error C2109: 下标要求数组或指针类型
    i[b] = 5;   // 失败 error C2109: 下标要求数组或指针类型
    b[i] = 6;   // 失败 error C2109: 下标要求数组或指针类型

  因此编译器对于数组的处理时,必须要有一个指针变量作为基址,其它数值作为数组下标。你可能不禁会问:如果存在两个指针变量,怎么办? 按照上面的推测,存在两个指针变量,编译器貌似是无法处理的,实验验证也表明,存在多个指针变量,将会报错:

    int a[5],b[5];
    a[b+2] = 4;  // 失败  error C2107: 非法索引,不允许间接寻址

至此问题分析完毕,总结如下:

  1. a[i] 与 i[a]的含义一样
  2. 数组a[i]被解释为: ptr [数组首地址 + 下标 * sizeof(变量类型)]
  3. 表达式中必须出现一个指针变量,无论其位置如何,其被视为数组首地址,剩余的值作为数组下标

版权声明:本文为博主原创文章,未经博主允许不得转载。 博客原址:http://blog.csdn.net/sunliymonkey

时间: 2024-11-05 22:48:50

在c/c++语言中,为什么c[5] == 5[c]的相关文章

在Swift语言中,关于Any,AnyObject,AnyClass的区别与联系

在Swift语言中,协议定义类或结构体应该遵守的变量和方法集合,如下所示,这个一个标准的协议的声明: protocol NSObjectProtocol { func isEqual(object: AnyObject?) -> Bool var hash: Int { get } var superclass: AnyClass? { get } func `self`() -> Self! func isProxy() -> Bool func isKindOfClass(aClas

GO语言中import的规则和用法

GO语言中引入包使用import,我将在本文讲解下规则和用法. 一些规则: 1.包中不能有main方法. 2.同文件夹中可以直接用方法名调用. 3.main函数建议放在package main里4.main不能调用同个目录下的其它文件中的方法. 5.还可以把包放在上级的目录中,如: /src/myFolder/foo/bar1.go #package foo /src/myFolder/foo/bar2.go #package foo /src/myFolder/foo/bar3.go #pac

Java语言中String累的总结

String类 1.Java.lang包简介 java.lang包是java内置的一个基础包,其中包含了一系列程序中经常要用到的类: 在默认情况下,每个java程序都会自动导入该包,因此无需在程序中显式地声明. 2.String类 Java语言中,字符串是String类的对象: Java语言中,String是引用数据类型: 可以通过使用String类提供的方法来完成对字符串的操作: 创建一个字符串对象之后,将不能更改构成字符串的字符: 每当更改了字符串版本时,就创建了一个新的字符串对象,并在其内

C语言中,头文件和源文件的关系(转)

简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析阶段 3.编译阶段,首先编译成纯汇编语句,再将之汇编成跟CPU相关的二进制码,生成各个目标文件 (.obj文件)4.连接阶段,将各个目标文件中的各段代码进行绝对地址定位,生成跟特定平台相关的可执行文件,当然,最后还可以用objcopy生成纯二进制码,也就是去掉了文件格式信息.(生成.exe文件) 编译器在编译时是以C文件为单位进行的,也就是

C语言中,定义的含义?声明的含义?它们之间的区别是什么?

在C语言中,对于定义和声明,也许我们非常的熟悉,但不一定真正的了解! 定义的含义:所谓定义,就是创建(编译器)一个对象,为这个对象分配一块内存空间并取名,也就是我们平常所说的变量名或对象名,一旦这个名字和这块内存空间匹配,那么在定义的这个对象或变量的生命周期中,所创建的这个变量名将不能再被改变,并且内存空间的位置也不会改变.在一个区域内(函数内,全局),一个名字只能被定义一次,不能重复定义. 声明的含义:声明有两重含义 第一重含义:告诉编译器,这个名字已经匹配到了一块内存空间上,后面的代码所用到

黑马程序员---C语言中的extern

------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”. 1. extern修饰变量的声明.举例来说,如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v.这里需要注意的是,被引用的变量v的链接属性必须是外链接(external)的,也就是说a.c要引用到v,不只是取决于在a.c中

转:C语言中的static变量和C++静态数据成员(static member)

转自:C语言中的static变量和C++静态数据成员(static member) C语言中static的变量:1).static局部变量        a.静态局部变量在函数内定义,生存期为整个程序运行期间,但作用域与自动变量相同,只能在定义该变量的函数内使用.退出该函数后, 尽管该变量还继续存在,但不能使用它.        b.对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值.而对自动变量不赋初值,则其值是不定的.2).static全局变量        全局变量本身就是静

Swift语言中 String 的一些操作方法

整理自慕课网liuyubobobo老师在<玩儿转Swift>课程中的讲解,在此表示感谢!同时推荐这套视频课程http://www.imooc.com/learn/127 var str = "Welcome to Play Swift! Step by step to learn Swift." // Range的用法和相关字符串的操作代码片段  str.rangeOfString(“Step”)  // 字符串片段“Step”所处的字符范围 23..<27, 也即第

c语言中struct的初始化

C++中的struct已经和class一样,可以用构造函数初始化. C语言中的struct怎么初始化呢? typedef struct _TEST_T {        int i;        char c[10];}TEST_T; TEST_T gst  = {1, “12345”};//可以初始化,设置i为1,s为一个字符串. TEST_T gst  = {1};//初始化个数少于实际个数时,只初始化前面的成员. TEST_Tgst  = {.c=“12345”};//有选择的初始化成员

C语言中volatilekeyword的作用

一.前言 1.编译器优化介绍: 由于内存訪问速度远不及CPU处理速度,为提高机器总体性能,在硬件上引入硬件快速缓存Cache,加速对内存的訪问.另外在现代CPU中指令的运行并不一定严格依照顺序运行,没有相关性的指令能够乱序运行,以充分利用CPU的指令流水线,提高运行速度.以上是硬件级别的优化.再看软件一级的优化:一种是在编写代码时由程序猿优化,还有一种是由编译器进行优化.编译器优化经常使用的方法有:将内存变量缓存到寄存器:调整指令顺序充分利用CPU指令流水线,常见的是又一次排序读写指令.对常规内