本文共 2612 字,大约阅读时间需要 8 分钟。
规则1:数组 = 指向数组首地址的指针常量 + 数组元素
简单说就是,数组 = 指针常量 + 数组内容, 数组名就是这个指针常量,这个指针常量一切语法意义都原样保留了(只有一个特例,就是对这个指针常量取地址)。 对数组取地址操作,其实取出的是“指向数组首地址的指针常量”的地址值,但由于 “指向数组首地址的指针常量”已经被优化掉了——没有实体了,所以会被用数组内容的首地址来替代。由于指针常量是常量,编译器觉得你这玩意没必要实际占用空间,所以剥夺了指针常量的空间分配,但是所有的语法意义都保留了。 语法上多维数组看起来完全按照规则1来的。 规则2:编译器会把一切编译时刻已经确定的东西尽可能只保留其语法意义的同时确保其不占用实际内存空间 这条规则更普适。 第二条本质是一个编译器优化规则,就是说,如果编译时刻,信息都是确定的,那么我只要保留语法意义就行了(因为语法意义只有编译器去遵守,并不需要占用存内存空间去保存,属于“虚”的东西),只有绕不开才分配。 举个例子:你有个函数指针,你声明为const,那么通过这个函数指针访问函数的地方就会变成直接对函数的调用,而这个函数指针实体是不会存在的——这就是用到了,但绕的开。如果你作死,对这个函数指针取地址,并且这个地址还被赋值给别的变量——那就是用到了,且绕不开,那真的就没办法,只能分配一个实体了。 这个原则对编译器来说是优化的指导原则,所以,不开优化,开不同等级的优化,不同编译器,都有可能有行为上的差异,但数组这个东西是定死这么玩的,所以无论你开不开优化,那个 “地址常量”都不会有实体了。看个例子:
#includeint main() { int a[5] = { 1,2,3,4,5 }; int *ptr = (int *)(&a + 1); printf("a=0x%p\n", a); printf("&a=0x%p\n", &a); printf("%d,%d,0x%x", *(a + 1), *(ptr - 1), (unsigned int)a + 1); return 0;}
输出结果:
代码分析:看个例子:
#includevoid func(void){ printf("hello.\n");}int main(void){ printf("func=%x\n", func); printf("&func=%x\n", &func); return 0;}
输出结果:
代码分析:再分析个例子:
想让程序跳转到绝对地址0x100000处执行,该如何做? 答案为:(*(void(*) ())0x100000)()((void(*) ())0x100000)()//写成这样也对
代码分析:
按照&运算符本来的意义,它要求其操作数是一个对象,但函数名不是对象(函数是一个对象),本来&func是非法的,但很久以前有些编译器已经允许这样做,c/c++标准的制定者出于对象的概念已经有所发展的缘故,也承认了&func的合法性。同理,数组也是一样的道理。
所以,既然 & 不 & 都可以,那么* 不 * 也都可以。 这样也就解释了为什么( * (void( * ) ())0x100000)()或者((void( * ) ())0x100000)()都对。 对于以函数名或者数组名作为函数的参数来说,& 不& 都无所谓, 对于函数调用来说,函数名字前加不加 * 都无所谓。 但是对于数组,&a+1,和a+1作为右值来说还是要加以区分,&a+1相当于指针+1(指向下个数组),a+1相当于地址加1(指向下个元素),(unsigned int)a+1相当于数值+1,结果是不一样的也可以将指向数组首地址或函数体的指针常量存储到内存的断中,进行一些自动初始化的操作。
转载地址:http://fdnii.baihongyu.com/