从朋友博客上看到有如下一个有趣的笔试题:
struct s{
int a;
int b;
char c[8];
void* d;
}mystruct;
1,问&((struct s*)0)->d出现设么结果?
2,这种类型的代码一般出现在什么情况下?
答案是:
1。16。
2。取结构内元素的偏移时用。
这种取结构内元素的偏移的写法是第一次看到,非常令人惊奇。为了更好地理解它,下面仔细地看看这是怎么回事。
首先想到的是,0代表着NULL,涉及到指针的时候NULL总是比较可怕的,可是这里却异常地安全,这到底是怎么回事?这里的0是个常量,把它换成其它的常量(比如字符串常量),初始化的变量,换成未初始化的变量还会得到这个输出结果吗?编译器是如何处理它的?
这里都是用的->运算符,换成.运算符可以吗?->和.运算符在编译器生成代码的时候有哪些不同的表现?这里的这个struct比较特殊,不需要编译器进行内存地址对齐,那么对于那些不规范的struct,class , union, enum,这段代码会有什么样的表现?对于嵌套的strict,又会怎样?
于是写了如下一段测试代码:
#include <stdio.h>
struct s{
int a;
int b;
char c[8];
void* d;
}mystruct;
struct alignMem {
char ch;
int i;
double f;
};
struct nested {
int i;
char ch;
struct alignMem a;
struct {
int n;
char cc;
} test;
};
#define p(exp) printf("%30s:/t%X/n/n", #exp, exp)
int main() {
int i;
p(&((struct s*)0)->d);
p(&((struct s*)1)->d);
p(&((struct s*)NULL)->d);
p(&((struct s*)10)->d);
p((struct s*)NULL);
p((struct s*)1);
printf("i is a local variable without initialized:/n");
p(((struct s*)&i));
p(&((struct s*)&i)->d);
i = 0;
printf("i is initialized to 0:/n");
p(&((struct s*)&i)->d);
i = 1;
printf("i is initialized to 1:/n");
p(&((struct s*)&i)->d);
printf("const char[] as argument:/n");
p(&((struct s*)"a")->d);
printf("alignmen:/n");
p(&((struct alignMem*)0)->f);
printf("nested:/n");
p(&((struct nested*)0)->test.cc);
return 0;
}
为了看看编译器是怎么处理的,需要看看编译器生成的汇编代码是如何的,这里的编译器用的是VC 6.0。在命令行下用如下命令编译:
cl /Faasm.txt /FAcs /Fmmap.txt /Tctest.cpp
其中/Fa表示生成汇编代码,/FAcs表示生成的汇编代码中包括机器指令和源代码,/Tc表示强制使用C语言编译器而不是C++编译器来编译。
运行test.exe,输出结果如下:
&((struct s*)0)->d : 10
&((struct s*)1)->d : 11
&((struct s*)NULL)->d : 10
&((struct s*)10)->d : 1A
(struct s*)NULL : 0
(struct s*)1 : 1
i is a local variable without initialized:
((struct s*)&i) : 12FF7C
&((struct s*)&i)->d : 12FF8C
i is initialized to 0:
&((struct s*)&i)->d : 12FF8C
i is initialized to 1:
&((struct s*)&i)->d : 12FF8C
const char[] as argument:
&((struct s*)"a")->d : 4071F0
alignmen:
&((struct alignMem*)0)->f : 8
nested:
&((struct nested*)0)->test.cc : 1C
测试结果,我感觉应该能表示这是编译器相关而不是语言相关的东西。而且这应该是一个好的编译器理所当然有的,感觉。上面凡是有地址的,输出的都是和内存地址相关的,那么必须得作一次减法才能得到偏移地址,凡是无地址的,得到的都是“净”的量。
编译器内部都会维护关于类型的信息。从产生的汇编代码也可以看到,凡是(struct s*)0这样的写法,这些值都是在编译时确定的,而用到变量i的时候,都是在运行时确定的。
不管怎么样,这个题目让人学到太多的东西了。