目录
一.offsetof的功能二.自定义offsetof三.探索结构体3.1结构体中的成员3.2结构体指针和数组该篇博客主要讲了自定义offsetof是如何实现的,为什么要这样写,以此沿生出对结构体偏移量产生的内存状态,和对结构体中指针和数组区别的探索,这里是给我启发和帮助的一篇博客-----C语言结构体里的成员数组和指针,这篇博客是一位行业大佬写的,正是这篇博客让我看到了在结构体中更深层的东西,建议大家去读一下原文。
一.offsetof的功能
用来确定结构体中各成员变量的偏移量。如果对这块知识不熟悉可以看结构体进阶详解这篇博客。
#include<stddef.h>typedef struct Student{int age;char name[10];char sex[10];}Stu;int main(){printf("%d %d\n", offsetof(Stu, age),offsetof(Stu,name));return 0;}
二.自定义offsetof
#define MY_offsetof(TYPE, MEMBER) (size_t)(&((TYPE*)0)->MEMBER)typedef struct Student{int age;char name[10];char sex[10];}Stu;int main(){printf("%d %d\n", MY_offsetof(Stu, age),MY_offsetof(Stu,name));return 0;}
这个时候大家应该会有很多的疑惑,为什么这样写就能得到成员变量对应的偏移量。为什么要取地址为什么又要对它进行强制类型转换
接下来让我们慢慢探索这些问题!
三.探索结构体
首先我们先看一个简单的代码:
typedef struct Student{int age;char name[10];}Stu;int main(){Stu *st = NULL;if (st->name){printf(st->name);}return 0;}
当我们将这段代码放入编译器,进行调试时,很容易发现,到12行时,就会报错。那为什么在if语句里它能正确运行,而到了12行,却会报错呢?
这个问题先不急着回答,当我们对代码进行小小的修改,将printf函数改为如下形式:
printf("%d\n", st->name);
我们会得到数字4.
这就解释了为什么在if语句中可以运行通过了。而在12行中,只写st->name只有在该成员变量存储为字符串的时候可以正常运行,而该代码明显不行。
新的问题就出现了,为什么会输出4呢?
3.1结构体中的成员
首先,我们要明白,变量,就是内存地址的一个抽象名字而已。在静态编译的程序中,所有的变量名都将转化为机器能够识别的地址。
所以有了——栈内存、堆内存、静态内存、常量内存,我们代码中的所有变量都会被编译器预先放到这些内存区中。
有了这些基础,我们来看一下如下结构体中的成员变量的地址是什么?
typedef struct Student{int age;int phone[10];int* phone2;double name;}Stu;Stu s;
如图是在VS中调试的结果,我们可以看到各成员变量的地址。
s的地址为起始地址,age的地址与s变量的地址相同,其余成员变量都是正常发生了偏移,是对于s的相对地址。
当我们输出这些地址时:
printf("%d\n", &(s->age));printf("%d\n", s->phone);printf("%d\n", &(s->phone2));printf("%d\n", &(s->name));
如此我们便得到了,各个成员变量的偏移量了,这也是为什么之前输出为4的原因,输出的为它的相对地址。
那我们在现在就会过头来在看一下自定义的offsetof:
#define MY_offsetof(TYPE, MEMBER) (size_t)(&((TYPE*)0)->MEMBER)
当我们将0强制类型转换为结构体类型,调用它的成员变量在取地址就能拿到它对应的偏移量。
而是否强制类型转换,其实影响不是很大,但为了更好的输出还是加上的好。
既然讲到了这里,我们在上面的也看到了在结构体中指针和数组似乎并无区别,但在我查阅资料和前辈所写的博客后,却并非如此(博客链接放到最开始的位置)。
3.2结构体指针和数组
当我们定义如下两个结构体:
//结构体1struct Student{int age;char phone[10];};int main(){struct Student* s1= (struct Student*)malloc (sizeof (struct line));s1->length = 10;memset(thisline->contents, 'a', 10);return 0;}
//结构体2struct Student{int age;char* phone;};int main(){struct Student* s2 = (struct Student*)malloc (sizeof (struct line));s2->phone = (char*) malloc( sizeof(char) * 10);s1->length = 10;memset(thisline->contents, 'a', 10);return 0;}
我们使用gbd调试后,可以清楚的看到它的内存的变化。
先来看一下结构体1,age占用4个字节的内存,phone[]占用10个字节的内存,一共占用14个字节的内存。
(这里的内存代码可能出现错误,但对于了解这方面知识不会产生影响)
(gdb) x /14b s10x601010: 100 0 0 979797970x601018: 979797979797
前4个字节的内存是存放成员变量age的,后10个字节是存放phone[]的。
在看一下结构体2,与1相同,age占用4个字节的内存,指针phone开辟了10个字节。
(gdb) x /16b s20x601010: 1 0 0 0 3216960(gdb) x /10b this->phone0x601020: 97979797979797970x601028: 9797
我们可以清晰的看到,共占用了3行内存。
- 第一行前4个字节是int age。- 第一行后四个字节存放的是指针phone所存放内容的地址。- 它的地址是0x20 0x10 0x60,按小端存储,地址为0x601020- 第2和3行是指针phone所指向的内容。
这里我们就很清楚的看到了结构体中指针和数组的区别。