该篇博客主要讲了自定义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;
如图是在VS2019中调试的结果,我们可以看到各成员变量的地址。
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结构体指针和数组
当我们定义如下两个结构体:
//结构体1
struct 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;
}
//结构体2
struct 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 s1
0x601010: 10 0 0 0 97 97 97 97
0x601018: 97 97 97 97 97 97
前4个字节的内存是存放成员变量age的,后10个字节是存放phone[]的。
在看一下结构体2,与1相同,age占用4个字节的内存,指针phone开辟了10个字节。
(gdb) x /16b s2
0x601010: 1 0 0 0 32 16 96 0
(gdb) x /10b this->phone
0x601020: 97 97 97 97 97 97 97 97
0x601028: 97 97
我们可以清晰的看到,共占用了3行内存。
- 第一行前4个字节是int age。
- 第一行后四个字节存放的是指针phone所存放内容的地址。
- 它的地址是0x20 0x10 0x60,按小端存储,地址为0x601020
- 第2和3行是指针phone所指向的内容。
- 这里我们就很清楚的看到了结构体中指针和数组的区别。