# 工欲善其事必先利其器-C语言拓展--嵌入式C语言(三)

简介: # 工欲善其事必先利其器-C语言拓展--嵌入式C语言(三)

工欲善其事必先利其器-C语言拓展–嵌入式C语言(三)

typeof()

使用typeof可以获取一个变量或表达式的类型。

typeof的参数有两种形式:表达式或类型。

int i;
typeof(i) j = 20; --> int j = 20;
typeof(int *) a; -->int *a;
int f();         -->typeof(f()) k;--? int k

我们可以看出通过typeof获取一个变量的类型int后,可以使用该类型再定义一个变量。

高级用法:

typeof (int *) y;-->int *y  y是一个指向int类型的指针
typeof (int) *y;     执行int类型的指针变量y
typeof (*x) y;       y是一个指向x类型的指针  (这下是不是对前面两个的小小区别有所感悟)
typeof (int) y[4];    y这个数组元素的类型是int  (换成x)就是x的类型   int y[4]   
typeof (*x) y[4];   *x y[4]
typeof (typeof (char *)[4]) y;//-->char *y[4]  字符指针数组  这个里面的数组元素都是 指针变量 
typeof (char *)[4] --> char *[4]   type (char *)类型不就是括号里面的   然后 在加一层-->
typeof(int x[4]) y; int y[4]
对于以上其实观察的时候就一层层的剥开即可

typeof对于数组的操作还是挺花哨的,惊艳了。

container_of宏

Linux内核第一宏:container_of

听起来这么牛的宏,我们来一睹芳容一下

#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));})

怎么样,内核里到处都是这。

宏中有宏。

三个参数:

  • type: 结构体的类型
  • member: 结构体成员
  • ptr: 结构体成员member地址

这个宏的作用:

通过结构体的某一成员的地址,来获取这个结构体的首地址。

这下再看是不是大概还是好点了。

我们来看个实用例子。

struct student
{
  int age;
  int num;
  int math;
};
int main(void)
{
  struct student stu;
  struct student *p;
  p = container_of(&stu.num, struct student, num);
  return 0;
}

定义一个结构体类型student,

然后定义一个结构体变量stu,

知道了结构体成员变量stu.num的地址,

那么我们就可以通过container_of宏来获取结构体变量stu的首地址。

container_of这个玩意有什么用呢?

因为内核中有很多的结构体类型数据,为了抽象,会对结构体进行多次的封装,往往一个结构体里面又包含了多个结构体。无限套娃。不同的层次,不同的模块,使用的是对应的不同封装程度的结构体。

这个是不是有点让你联想到面向对象思想,没想到?没事我知道你认真专注于当下。

这样的优点我就不多说了。面向对象的优点。

在内核中,我们传递个函数的参数是某个结构体的成员,在这个函数中,还想用这个结构体的其他变量,这不是就需要container_of出场了。

找到了这个结构体的首地址–>

offsetof()函数主要用来计算member成员相对于结构体起始地址的偏移量。

现在我们来详细看看这个宏定义:

这里需要你先知道结构体怎么存储的。

结构体作为一个复合类型数据,它里面可以有多个成员。当我们定义一个结构体变量时,编译器要给这个变量在内存中分配存储空间根据每个成员的数据类型和字节对齐方式,编译器会按照结构体中各个成员的顺序,在内存中分配一片连续的空间来存储它们。

将数字0通过强制类型转换,转换为一个指向结构体类型为student的常量指针,然后分别打印这个常量指针指向的各成员地址。运行结果如下。

因为常量指针的值为0,即可以看作结构体首地址为0,所以结构体中每个成员变量的地址即该成员相对于结构体首地址的偏移。

知道了结构体的相对偏移地址,用结构体成员的地址减去相对偏移,就可以得到结构体的首地址。

从语法角度来看,container_of宏的实现由一个语句表达式构成。语句表达式的值即最后一个表达式的值。

取结构体某个成员member的地址,减去这个成员在结构体type中的偏移,运算结果就是结构体type的首地址。

因为语句表达式的值等于最后一个表达式的值,所以这个结果也是整个语句表达式的值,container_of最后会返回这个地址值给宏的调用者。

结构体的成员数据类型可以是任意数据类型,为了让这个宏兼容各种数据类型,我们定义了一个临时指针变量__mptr,该变量用来存储结构体成员MEMBER的地址,即存储宏中的参数ptr的值。如何获取ptr指针类型呢。

#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));})
三个参数:
  + type: 结构体的类型
  + member: 结构体成员
  + ptr: 结构体成员member地址
  + 指针变量__mptr:存储结构体成员MEMBER的地址-->ptr

我们知道,**宏的参数ptr代表的是一个结构体成员变量MEMBER的地址,**所以ptr的类型是一个指向MEMBER数据类型的指针,当我们使用临时指针变量__mptr来存储ptr的值时,必须确保__mptr的指针类型和ptr一样,是一个指向MEMBER类型的指针变量。

确保__mptr的指针类型和ptr一样

typeof(((type*)0)->member)表达式使用typeof关键字,用来获取结构体成员MEMBER的数据类型,然后使用该类型,通过typeof(((type*)0)->member)*__mptr这条程序语句,就可以定义一个指向该类型的指针变量了。

在语句表达式的最后,*因为返回的是结构体的首地址,所以整个地址还必须强制转换一下,转换为TYPE,即返回一个指向TYPE结构体类型的指针,*所以你会在最后一个表达式中看到一个强制类型转换(TYPE

这个文章也写的不错,讲的蛮清楚的。

一个parent代表结构体的类型,name代表结构体中的成员。

*((parent)0),**把数字0强制转换成parent 结构体指针类型。这样 ((parent )0) 这个整体就相当于一个指针指向了 0 这个地址,不管 0 这个地址是否合法,是否真的有这么一个结构体对象,它都会把以 0 地址为首的一片连续内存当成一个结构体对象操作。

((parent)0)->name,* *结构体指针((parent)0)取结构体对象中name成员。因为这只是对内存操作,**并没有写内存,虽然地址不合法也不会出现段错误。

*&((parent )0)->name对name成员取地址。

*offset = (uint32_t)&((parent )0)->name偏移量。因为这个parent类型结构体对象是从0地址开始的,故而offset就是成员name的偏移量。

知道成员偏移量,就很容易求结构体对象本身的地址。成员的地址减去偏移量就是是结构体对象的首地址! – 本来的变量地址-便宜地址就是开始地方

((parent*)0)((uint32_t)node - (uint32_t)&((parent*)0)->name)结构体对象的首地址。

然后就可以开始访问变量了。->

到这里你应该懂了这个原理

[学习资料]:(https://www.freesion.com/article/57261214164/)

《嵌入式C语言自我修养:从芯片、编译器到操作系统》

目录
相关文章
|
28天前
|
机器学习/深度学习 算法 数据挖掘
C语言在机器学习中的应用及其重要性。C语言以其高效性、灵活性和可移植性,适合开发高性能的机器学习算法,尤其在底层算法实现、嵌入式系统和高性能计算中表现突出
本文探讨了C语言在机器学习中的应用及其重要性。C语言以其高效性、灵活性和可移植性,适合开发高性能的机器学习算法,尤其在底层算法实现、嵌入式系统和高性能计算中表现突出。文章还介绍了C语言在知名机器学习库中的作用,以及与Python等语言结合使用的案例,展望了其未来发展的挑战与机遇。
45 1
|
28天前
|
人工智能 安全 算法
基于C语言的嵌入式系统开发,涵盖嵌入式系统概述、C语言的优势、开发流程、关键技术、应用实例及面临的挑战与未来趋势。
本文深入探讨了基于C语言的嵌入式系统开发,涵盖嵌入式系统概述、C语言的优势、开发流程、关键技术、应用实例及面临的挑战与未来趋势。C语言因其高效、可移植、灵活及成熟度高等特点,在嵌入式系统开发中占据重要地位。文章还介绍了从系统需求分析到部署维护的完整开发流程,以及中断处理、内存管理等关键技术,并展望了嵌入式系统在物联网和人工智能领域的未来发展。
67 1
|
6月前
|
C语言
C语言实现2048小游戏---粤嵌GE6818嵌入式系统实训
C语言实现2048小游戏---粤嵌GE6818嵌入式系统实训
|
6月前
|
安全 Unix Linux
嵌入式C语言(十四)
嵌入式C语言(十四)
48 0
|
4月前
|
存储 缓存 编译器
【C语言篇】scanf和printf万字超详细介绍(基本加拓展用法)(下篇)
scanf处理⽤⼾输⼊的原理是,⽤⼾的输⼊先放⼊缓存,等到按下回⻋键后,按照占位符对缓存进⾏解读。 解读⽤⼾输⼊时,会从上⼀次解读遗留的第⼀个字符开始,直到读完缓存,或者遇到第⼀个不符合条件的字符为⽌。
194 2
|
4月前
|
存储 C语言
【C语言篇】scanf和printf万字超详细介绍(基本加拓展用法)(上篇)
printf 的作⽤是将参数⽂本输出到屏幕。它名字⾥⾯的 f 代表 format (格式化),表⽰可以定制输出⽂本的格式。
94 1
|
4月前
|
算法 IDE 程序员
C语言与嵌入式系统:嵌入式C编程基础。
C语言与嵌入式系统:嵌入式C编程基础。
93 0
|
6月前
|
存储 移动开发 C语言
技术心得记录:嵌入式开发中常用到的C语言库函数
技术心得记录:嵌入式开发中常用到的C语言库函数
70 1
|
6月前
|
C语言
C语言实现电子音乐相册---粤嵌GEC6818嵌入式系统实训
C语言实现电子音乐相册---粤嵌GEC6818嵌入式系统实训
|
6月前
|
安全 Linux 编译器
嵌入式C语言(十二)
嵌入式C语言(十二)
42 1