分享

C语言中那些重要而又易被忽视的知识点

 星之梦灬逝去爱 2018-04-12

1 共用体

共用体也叫共同体、联合体,本文使用共用体这个命名。整体而言,共用体不难理解,我们之所以觉得共用体难是因为它不常用,虽然不常用,但这并不代表它不重要,很多时候使用共用体,功能更容易实现。

共用体是一种特殊的数据类型,允许我们在相同的内存位置存储不同的数据类型。我们可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。在共用体中,各成员共享同一段内存空间,一个共用体变量的长度等于各成员中最长的长度,需要说明的是,这里所谓的共享不是指多个成员同时装入一个联合变量内,而是指该共用体变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值。

因为共用体与结构体的相似性和差异性,我们不妨把这两个放到一起,对比结构体和共用体各成员的地址、值,以及结构体和共用体变量的长度,以此来分析系统是怎么对结构体和共用体进行内存分配的,如下图

从程序运行的打印信息可以看到:(1)结构体的成员a和b的地址不一样,而共用体的两个成员的地址完全一样;(2)结构体成员a和b的值为最初赋值的值,而共用体先给成员a赋值,再对成员b赋值,而最终的共用体值为成员b的值,即成员b覆盖了成员a;(3)结构体变量的长度是8,共用体变量的长度是4;下图展示内存分配对比

首先,需要明确,计算机对内存的管理是以“字”为单位的(许多计算机系统以4个字节为一个“字”)。如果在一个“字”中只存放了一个字符,虽然只占一个字节,但该“字”中的其他3个字节不会接着存放下一个数据,而会从下一个“字”开始存放其他数据。因此在用sizeof运算符求结构体 student 的长度时,得到的不是理论值1+4=5,而是8,必然是4的整数倍。

分析:结构体变量所占内存长度是各个成员占的内存长度之和,每个成员分别占有其自己的内存单元。而共用体变量所占的内存长度等于最长的成员的长度。因此我们可以看到上图中,结构体成员各自分配有内存,而共用体是按照最长的成员b分配内存。

下面再看一个综合性例子,请根据执行打印信息自行分析

小编给大家推荐一个学习氛围超好的地方,C/C++交流企鹅裙:三四一六三六七二七!适合在校大学生,小白,想转行,想通过这个找工作的加入。裙里有大量学习资料,有大神解答交流问题,每晚都有免费的直播课程

2 typeof

typeof 关键字是 C 语言的一个新扩展,这个关键字在 linux 内核中应用非常广泛。typeof 关键字是获取其参数的类型,typeof 的参数可以是两种形式:表达式或类型。如果 typeof 的参数是表达式,则表达式不会执行,只会得到该表达式的类型。

先来看一个实例

分析:我们可以看到 typeof(func(m, n)) chr; 这里并没有打印“a + b = 3”,说明func()并没有执行,而只是定义了和 func() 一样类型的变量 chr。

通过上面的分析,我们可以初步了解 typeof 关键字,下面再看几个实例:

(1)把 y 定义成与 x 一样的数据类型:

typeof (x) y;

(2)把 y 定义成与 x 一样的数据类型的数组:

typeof (x) y[8];

(3)typeof(int [8]) y; 等价于 int y[8];

(4)typeof(int *) x, y; 等价于 int *x, *y;

(5)typeof(int) *x, y; 等价于 int *x, y;

typeof 关键字在 linux 内核中应用非常广泛,下面看看linux内核中include/linux/kernel.h 这个头文件下的一个应用,如下图

这里宏定义 max 的作用是从两个相同类型的对象中选取较大的,它接受两个参数,先用 typeof 定义两个变量 _max1, _max2,分别和 x, y的类型相同,并分别赋值为 x ,y。这里用 typeof 的作用就是可以让 max 接受任何类型的参数而不必局限于某一种单一类型。接着 (void) (&_max1 == &_max2); ,这里主要是检测两个变量的类型是否相同,因为我们并不能保证用户在使用 max 时传入的参数是相同类型的,因此取参数地址,用指针类型来比较,如果两个指针的类型不一致,编译器就会产生警告(不报error,只报warning)以达到检测的效果,至于前面的 (void),是因为仅表达式 &_max1 == &_max2 本身是没有意义的,如果没有这个 (void) 编译器同样会警告。最后一句用来条件运算符来返回较大的值。

再来看看 linux 内核中include/linux/stddef.h这个头文件下的一个应用,这是一个非常重要和经典的应用:通过结构体成员的地址获取结构体变量的地址。

分析:我们先看 offsetof 宏,这个宏接受两个参数,参数 TYPE 代表结构体类型,参数 MEMBER 代表结构体中的成员,((TYPE *)0) 这部分,它把0强制转换成 TYPE * 型的指针类型,这样就有一个结构体类型指针指向了0地址,然后看 ((TYPE *)0)->MEMBER) ,这里 ((TYPE *)0) 指针取结构体对象中的 MEMBER 成员,这样就可以用&取 MEMBER 成员的地址了,因为结构体起始地址是0,这样 MEMBER 成员的地址也就是偏移了,整个 ((size_t)&((TYPE *)0)->MEMBER) 就取到了结构体成员相对结构体变量的偏移地址。接下来再看container_of 这个宏,它接受3个参数,第一个 ptr 代表已知成员的地址,第二个 type 代表结构体的类型,第三个 member 代表已知的成员。这一句 const typeof( ((type *)0)->member ),用 typeof 获取了 member 成员的类型,用该类型定义了一个相应的指针变量 __mptr 并将其赋值为 ptr,然后 ( (char *)__mptr - offsetof(type,member) ) 这部分就是将 __mptr 强制转换为 char * 型,再减去 member 成员的偏移量就得到了该结构体对象的首地址,再把这个首地址强制转换为 type * 型也就是结构体本身的类型对应的指针类型,最终该结构体对象的地址就求出来了。是不是感觉处处是艺术。

3 函数指针

我们经常使用函数指针实现回调函数,回调函数使用太广泛,这里不多说直接上例程。

这里先定义一个函数指针 p ,先将 p 指向 addvalue 函数求加法,再将 p 指向 subvalue 求减法。可以看出函数指针 p 并不关心所指向的函数内部实现细节(做加法还是做减法),只要所指向的函数类型和函数指针一样(typedef char (*func)(char, char);),就可以用函数指针 p 来调用不同的函数。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多