首先我们要更正一个很熟悉的概念,那就是指针不仅仅是“地址”,指针还有一个很重要的特性,那就是“类型”。
指针初始化时,“=”的右操作数必须为内存中数据的地址,不可以是变量,也不可以直接用整型地址值(但是 int *p = 0; 除外,该语句表示指针为空);
所以 int *p = 10; 这样的代码是不允许的。在C++里面直接是error的,即使在一些C编译器中以warning的形式提示,但是warning有的时候也很严重。所以这种东西不要用。从const int到int*是不存在隐士转换的。
正确的使用方法是 int *p = (int*)10; 这样就先使10这个地址增加一个类型,然后在赋值给int *p。看这句话的汇编:
00401048 mov dword ptr [ebp-4],0Ah
就是把10放到p对应的内存空间去。
这里可以看出可以把一个地址加上一个类型,这样的话就构成了一个指针,但是这个指针类似一个常量,就像10这个立即数默认是int类型,不会产生代码,所以(int*)10也不会产生汇编代码。所以不会出现去访问内存的保护区域这种情况发生。
那么下面开始说利用宏定义求结构体成员偏移量
我们在书写C程序的时候,有时候需要根据结构体成员变量的地址,得到结构体的地址,特别是我们想用C来实现C++的继承特性的时候。
我们对问题的分析如下:
输入:一个结构体定义type,这个结构体中某个成员变量的名字member以及它的地址ptr 输出:包含此成员变量的结构体的地址
为了便于分析,我们给出一个实例来说明
struct father_t { int a; char *b; double c; }f; char *ptr = &(f.b); //而不是 ptr = f.b; 这里ptr是b的地址,而不是它指向的地址。 根据C语言对struct类型的存储特性,我们可以画这么一个图示:
注意:f.b是一个指针,指向char*b的第一个字符,&(f.b)是这个指针的地址。
通过分析图示,我们可以看出,我们只需要把当前知道的成员变量的地址ptr,减去它在结构体当中相对偏移量4就的到了结构体的地址(ptr-4)。
在linux当中对此有一个很好的宏可以使用,叫做 container_of, 放在 linux/kernel.h当中。它的定义如下所示:
#define offsetof(TYPE, MEMBER) ( (size_t) & ((TYPE *)0)->MEMBER )
注意:这个宏定义的输入是一个结构体的类型,这个结构体的一个成员变量。
宏功能:获得一个结构体变量成员在此结构体中的偏移量。 1. ( (TYPE *)0 ) 将零转型为TYPE类型指针; 2. ((TYPE *)0)->MEMBER 访问结构中的数据成员; 3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址,即相对于0的偏移量,要的就这个; 4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型,size_t应该最终为unsigned int类型。 此宏的巧妙之处在于将 0 转换成(TYPE*),这样结构体中成员的地址即为在此结构体中的偏移量。
这里只是利用0这个地址,使它具有类型,并没有访问0地址对应的内存空间里的数据,所以不会发生读写保护存储区这种情况。
#define offset(type, a) ( (unsigned int) &(((type*)0)->a) ) struct _test_ { int a; char x; double d; char ch[10]; }; int main() { cout << offset(struct _test_, ch) << endl; return 0; }
根据结构体内存对齐的方式,得到的结果正好是16。
参考文章:http://www.cnblogs.com/youxin/p/3348227.html