简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长!
优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀
优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀
优质视频课程:AAOS车载系统+AOSP14系统攻城狮入门实战课【原创干货持续更新中……】🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
🍉🍉🍉文章目录🍉🍉🍉
- 🌻1.前言
- 🌻2.C语言之结构体空指针与结构体空指针的地址介绍
- 🌻3.代码实例
- 🐓3.1 (frame_t *)0 等同于 frame_t *p = NULL;
- 🐓3.2 问题一:为什么访问p->f1发生段错误?而&(p->f1)则不会?
- 🐓3.3 问题二: (frame_t *)0 和 frame_t *p = NULL;区别?
- 🐓3.4 问题三: 根据打印,发现&(p->f1)打印等于8,正好是f1在frame_t中的偏移,而不是地址,为什么&(p->f1)表示的是f1成员变量在frame_t的偏移,而不是地址?
🌻1.前言
本篇目的:C语言之探秘:访问结构体空指针与结构体空指针的地址的区别
🌻2.C语言之结构体空指针与结构体空指针的地址介绍
- 在C语言中,结构体(Struct)是一种复合数据类型,它可以包含多个不同类型的数据项。指针(Pointer)是一种特殊的数据类型,用于存储变量地址。空指针(Null Pointer)是一个不指向任何有效内存地址的特殊指针。
- 当我们谈论结构体空指针与结构体空指针的地址时,我们需要明确两个概念:结构体变量的地址和结构体指针的地址。
-
- 结构体空指针:
- 结构体空指针是指未经初始化的结构体指针,它不指向任何有效的内存地址。在C语言中,我们可以使用
malloc
函数或calloc
函数为结构体指针分配内存。如果没有分配内存,那么这个结构体指针就是空指针。
struct Person {char *name;int age;
};
struct Person *p = NULL; // 声明一个结构体指针,并初始化为空指针
- 在这种情况下,
p
是一个空指针,它不指向任何有效的内存地址。 -
- 结构体空指针的地址:
- 结构体空指针的地址是指结构体指针变量在内存中的存储位置。在C语言中,每个变量都有一个唯一的地址。我们可以使用
&
操作符来获取变量的地址。
struct Person *p;
printf("%p\n", (void*)&p); // 输出结构体指针变量的地址
- 输出结果将是一个十六进制的地址值,表示
p
在内存中的位置。
- 结构体空指针与结构体空指针地址的区别:
- 结构体空指针是一个不指向任何有效内存地址的特殊指针,而结构体空指针的地址是指结构体指针变量在内存中的存储位置。这两者是不同的概念,不能混淆。
struct Person *p = NULL; // 空指针
printf("%p\n", (void*)p); // 输出空指针的值,即NULL
printf("%p\n", (void*)&p); // 输出空指针变量的地址
- 输出结果将是:
(nil)
0x7ff7bfeff8ac
- 在这里,第一个输出表示空指针的值,即NULL。第二个输出表示空指针变量
p
在内存中的地址。 - 在C语言中,结构体空指针与结构体空指针的地址是两个不同的概念。结构体空指针是一个不指向任何有效内存地址的特殊指针,而结构体空指针的地址是指结构体指针变量在内存中的存储位置。理解这两个概念有助于避免在编程过程中出现错误。
🌻3.代码实例
🐓3.1 (frame_t *)0 等同于 frame_t *p = NULL;
#include <stddef.h>
#include <stdio.h>
//#include <stddef.h>#undef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) );})typedef struct frame {int num;char name;float f1;
}frame_t;int main(void)
{frame_t fra, *pf;fra.num = 1;fra.name = 'M';fra.f1 = 1.23;//v1.0 (frame_t *)0用法printf("(frame_t *)0 = %p\n",(frame_t *)0);printf("(frame_t*)0->f1 = %p\n",&(((frame_t *)0)->f1));printf("(frame_t*)1->f1 = %p\n",&(((frame_t *)1)->f1));printf("(frame_t*)2->f1 = %p\n",&(((frame_t *)2)->f1));printf("(frame_t*)3->f1 = %p\n",&(((frame_t *)3)->f1));//v2.0 frame_t *p = NULL用法.frame_t *p = NULL;printf("p = %p\n",p);//printf("p = %ld\n",p->f1);//段错误printf("p = %p\n",&(p->f1));//v3.0int aa = 1;frame_t *q = (frame_t *)&aa;printf("q = %p\n",q);printf("q = %p\n",&(q->name));return 0;
}
打印:
(frame_t )0 = (nil)
(frame_t)0->f1 = 0x8
(frame_t*)1->f1 = 0x9
(frame_t*)2->f1 = 0xa
(frame_t*)3->f1 = 0xb
p = (nil)
p = 0x8
q = 0x7ffe13c1f4a4
q = 0x7ffe13c1f4a8
计算地址:
(frame_t*)0->f1 = 0x8 + 0 = 0x8 = 8
(frame_t*)1->f1 = 0x8 + 1 = 0x9 = 9
(frame_t*)2->f1 = 0x8 + 2 = 0xa = 10
(frame_t*)3->f1 = 0x8 + 3 = 0xb = 11
结论:
(frame_t*)0:等于指向0指针的偏移量 + 成员变量f1偏移量,等于8.
(frame_t*)0:等于指向1指针的偏移量 + 成员变量f1偏移量,等于9.
(frame_t*)1:等于指向2指针的偏移量 + 成员变量f1偏移量,等于10.
(frame_t*)2:等于指向3指针的偏移量 + 成员变量f1偏移量,等于11.
🐓3.2 问题一:为什么访问p->f1发生段错误?而&(p->f1)则不会?
答:
<1>.p->f1:打印指针 p 中 f1 成员的内容,但 p 是一个空指针,没有指向任何有效的内存。因此,访问 p->f1 会导致段错误。
<2>.&(p->f1) :打印指针 p 中 f1 成员的地址,不会导致段错误,因为它没有尝试访问 f1 的内容。
🐓3.3 问题二: (frame_t *)0 和 frame_t *p = NULL;区别?
<1>.在C语言中,frame_t *p = NULL; 将指针 p 初始化为空指针,意味着 p 指向的是一个特殊的空地址,不指向任何实际的结构体对象.访问 p 所指向的对象时,通常会导致未定义行为,可能会导致程序崩溃。
<2>.而 (frame_t *)0 是一个显式地将一个整数0转换为 frame_t 结构体指针类型的操作。这实际上将该指针设置为一个特定的地址,即空指针地址。
<3>.因此,(frame_t *)0 实际上与 frame_t *p = NULL; 的作用相同,都是将指针设置为空指针。
🐓3.4 问题三: 根据打印,发现&(p->f1)打印等于8,正好是f1在frame_t中的偏移,而不是地址,为什么&(p->f1)表示的是f1成员变量在frame_t的偏移,而不是地址?
答:
<1>.在C语言中,结构体的成员在内存中是连续存储的,因此可以通过计算偏移量来获取结构体成员的地址。
&(p->f1) 中的 & 运算符表示取地址操作,它取的是 f1 成员的地址,而不是简单的偏移量。
<2>.假设 p 指向一个有效的 frame_t 结构体对象,则 &(p->f1) 将返回 f1 成员的地址。
但是,如果 p 是空指针,则 &(p->f1) 仍然会尝试计算 f1 成员的地址,因为这是一个合法的操作,但是实际上不会返回有效的地址,因为它仅仅是一个空指针的偏移量加上 f1 成员在结构体中的偏移量。