【C语言】深度探索offsetof,解析结构体的成员数组和指针

该篇博客主要讲了自定义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所指向的内容。
  • 这里我们就很清楚的看到了结构体中指针和数组的区别。

猜你喜欢

转载自blog.csdn.net/m0_52094687/article/details/127477103