首先,回顾一下基础的宏操作:

C语言宏

###

  1. #的作用是字符串化:在一个宏中的参数前面使用一个#,预处理器会把这个参数转换为一个字符数组

    #define ERROR_LOG(info) fprintf(stderr,"error:"#info"\n");

    则有:

    ERROR_LOG("add"); ---> fprintf(stderr,"error: "add"\n");
    ERROR_LOG(devied =0); ---> fprintf(stderr,"error: devied=0\n");
  2. #是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。

    例如:

    #define XNAME(n) x##n

    那么XNAME(4)就会展开为x4.

do{/*codes*/}while(0)

采用这种方式是为了防范在使用宏过程中出现错误,主要有如下几点:

  (1)空的宏定义避免warning:

#define foo() do{}while(0)

  (2)存在一个独立的block,可以用来进行变量定义,进行比较复杂的实现。

  (3)如果出现在判断语句过后的宏,这样可以保证作为一个整体来是实现:

#define foo() \
action1(); \
action2();

  在遇到分支语句时:

if(NULL == pPtr)
 foo();

foo()中的两个语句就不会都被执行。

(4)为何不用单独的{}

#define switch(x,y) {int tmp; tmp=x;x=y;y=tmp;}
if(x>y)
switch(x,y);
else
op();

在把宏引入代码中,会多出一个分号,从而会报错。

变参宏: ···__VA_ARGS__

某些函数接受可变的参数例如printf(),在头文件stdvar.h中有工具可以自定义变参宏。

把宏参数列表中最后的参数用···省略,而__VA_ARGS__可用在替换部分,表面省略号代表的东西。

#define PR(···)  printf(__VA_ARGS__)

例如:

PR("THIS IS __VA_ARGS__");

会被展开为:

printf("THIS IS __VA_ARGS__");

预定义符号

符号 样例值 含义
__FILE__ "test.c" 进行编译的文件名
__LINE__ 25 当前行的行号
__DATE__ "Jan 31 2001" 被编译的日期
__TIME__ "23:17:24" 被编译的时间
__STDC__ 1 是否遵循ANSI C
__FUNCTION__ main 所在函数名称

这些宏与编译器有关,有些支持有些不支持.

如下程序:

运行结果为:

注意到: 如果用函数或内联函数,每次的行号便都会相同。

下面是内核中,常见的两个宏:

Linux常用的两个宏

offsetof

该宏的定义如下:

#define __offsetof__(type, member) ( ( size_t ) & ( ( type * ) 0 )->member )

作用是获取结构体某成员变量的偏移量。

分析如下:

  1. (type *)0 将0转化为该类型的指针,即地址为0x00000000
  2. ((type *)0)->member 访问成员member
  3. &(((type *)0)->member) 获取该成员地址(也就是其偏移量)
  4. (size_t)&(((type *)0)->member) 将地址转化为size_t类型 即偏移量

这里访问0指针为何不会报错,取决于gcc对于该过程的优化,不会直接访问空间而是直接获得地址.

container_of

该宏的定义如下:

#define __container_of__(ptr, type, member) ({\
const typeof ( ( ( type * ) 0 ) -> member ) *__mptr=(ptr);\
( type * )( ( char * )__mptr - __offsetof__( type, member ) );\
})

要理解这段宏,需要知道几个GCC C EXTENSIONS,查阅GCC MANUAL:

1.Statements and Declarations in Expressions



2.Referring to a Type with typeof



手册中也给出了一个典型的用法示例:

这段宏的分析如下:

  1. typeof()GNU C ,获得变量类型
  2. typeof (((type *)0 )->member) 起始地址为0 再获取member 最后返回member类型
  3. const typeof (((type *)0 )->member) * __mptr=(ptr) 定义 __mptr 指针,指向ptr指向的地址,并成为常量指针
  4. (char *)__mptr __mptr转化为字符型指针(运算以1个字节为单位)
  5. - __offsetof__(type,member)) 减去该成员的偏移量
  6. (type*)( ( char * )__mptr - __offsetof__(type,member)) 最后转化为指向该类型的指针(指向该类型的首地址)

关于上述两个宏的一段程序如下:

#include <stdio.h>
#include <string.h> /**
* 获取结构体变量成员的偏移量
* @param type 类型(struct)
* @param member 成员
*/
#define __offsetof__(type, member) ( ( size_t ) & ( ( type * ) 0 )->member ) /**
* 获取指向整个结构体的指针
* @param ptr 指向成员(member)变量的指针
* @param type 类型(struct)
* @param member 成员变量
*/
#define __container_of__(ptr, type, member) ({\
const typeof ( ( ( type * ) 0 ) -> member ) *__mptr=(ptr);\
( type * )( ( char * )__mptr - __offsetof__( type, member ) );\
}) typedef struct Student {
char gender;
int id;
int age;
char name[20];
double score;
} Stu; int main() {
int gender_offset,id_offset,age_offset,name_offset,score_offset;
gender_offset = __offsetof__(struct Student, gender);
id_offset = __offsetof__(struct Student, id);
age_offset = __offsetof__(struct Student, age);
name_offset = __offsetof__(struct Student, name);
score_offset = __offsetof__(struct Student, score);
printf("%d\t%d\t%d\t%d\t%d\n", gender_offset, id_offset, age_offset, name_offset, score_offset); Stu stu;
Stu *pstu;
stu.gender = '1';
stu.id = 9527;
stu.age = 18;
stu.score = 98.2;
strcpy(stu.name, "elioyang"); pstu = __container_of__(&stu.id, Stu, id); printf("gender=%c\n", pstu->gender);
printf("age=%d\n", pstu->age);
printf("name=%s\n", pstu->name);
printf("score=%lf", pstu->score); return 0;
}

运行结果如下:

0       4       8       12      32
gender=1
age=18
name=elioyang
score=98.200000

LinuxKernel(一)的更多相关文章

  1. visual studio 阅读 linux-kernel

    @2018-12-13 [小记] 使用 visual studio 阅读 linux-kernel 方法 a. 文件 ---> 新建 --->从现有代码创建项目 b. 指定项目存储位置,命 ...

  2. LinuxKernel优秀博客

    1.vanbreaker的专栏 2.LinuxKernel Exploration 3.DroidPhone的专栏 4.Linux内核研究以及学习文档和ARM学习以及研究的开放文档   [力荐] 5. ...

  3. linux-kernel 学习计划

    [资料] http://www.ibm.com/developerworks/cn/views/linux/libraryview.jsp http://www.kerneltravel.net/ [ ...

  4. linux-kernel/CodingStyle

    https://www.kernel.org/doc/Documentation/zh_CN/CodingStyle Chinese translated version of Documentati ...

  5. 如何在LinuxKernel中操作file(set_fs與get_fs)

    在Kernel 中,照理說能存取至 0 ~ 4GB.但是實作層面卻是只能讓我們使用到3GB ~ 4GB 這會導致我們無法使用open(),write()這些在user space下的function. ...

  6. 在阿里云中编译Linux4.5.0内核 - Ubuntu内核编译教程

    实验环境:Ubnuntu 64位(推荐使用14.04)+Xshell 阿里云现在提供的云服务器很好用的,用来编译内核性能也不错.本文介绍最基本的内核编译方法,为了方便,所有操作均在root用户下进行. ...

  7. C语言位域

    转载自 http://tonybai.com/2013/05/21/talk-about-bitfield-in-c-again/ 再谈C语言位域 五 21 bigwhite技术志 bitfield, ...

  8. linux I/O复用

    转载自:哈维.dpkirin url:http://blog.csdn.NET/zhang_shuai_2011/article/details/7675797 http://blog.csdn.Ne ...

  9. Linux内核分析作业7:Linux内核如何装载和启动一个可执行程序

            1.可执行文件的格式 在 Linux 平台下主要有以下三种可执行文件格式: 1.a.out(assembler and link editor output 汇编器和链接编辑器的输出) ...

随机推荐

  1. JPA 相关API (一)

    [Query 接口下的常用API] [API 测试类:Test_QueryAPI.java] 1 package org.zgf.jpa.entity; 2 3 import java.math.Bi ...

  2. POI Excel 导入导出重点

    HSSF是指2007年以前的,XSSF是指2007年版本以上的 这个还是比较好用的,这些总结来自Apache的官方向导的点点滴滴 详细的请参考http://poi.apache.org/spreads ...

  3. kubelet拉取pause镜像报错pull access denied for 172.20.59.190:81/kubernetes/pause-amd64, repository does not exist or may require 'docker login': denied

    目录 1 背景说明 2 现象 pod无法启动,一直显示ContainerCreating 3 问题分析 kubelet的启动参数如下 4 尝试的解决方法 4.1 本地docker login登录镜像仓 ...

  4. Deployment YAML方式创建Nginx服务

    replication controller Replication Controller为Kubernetes的一个核心内容,应用托管到Kubernetes之后,需要保证应用能够持续的运行,Repl ...

  5. [Luogu P1450] [HAOI2008]硬币购物 背包DP+容斥

    题面 传送门:https://www.luogu.org/problemnew/show/P1450 Solution 这是一道很有意思的在背包里面做容斥的题目. 首先,我们可以很轻松地想到暴力做背包 ...

  6. 关于C中指针的引用,解引用与脱去解引用

    *,& 在指针操作中的意义 (1)* 大家都知道在写int *p 时,*可以声明一个指针.很少人知道*在C/C++中还有一个名字就是"解引用".他的意思就是解释引用,说的通 ...

  7. AE(After Effects)的简单使用——记一次模板套用的过程

    一.环境配置 操作系统:win10 64位 软件版本:AE16.11版 二.模板下载 可去 newcger 网站进行模板下载,常用都是免费的,很好用,很推荐 三.具体操作 1.模板素材替换 点击[文件 ...

  8. 【SpringBoot】12.全局配置文件(properties)与yml配置文件

    一.SpringBoot全局配置文件 1.修改内嵌容器端口号 #application.properties server.port=8888 2.自定义属性的配置 使用@Value来给成员变量赋值 ...

  9. java实现一个简单的单链表反转

    自定义一个单链表,实现链表反转: 1.普通方法实现 2.递归方式实现 package listNode; public class ReverseNode { public static void m ...

  10. Flink基础:时间和水印

    ​ 往期推荐: Flink基础:入门介绍 Flink基础:DataStream API Flink基础:实时处理管道与ETL Flink深入浅出:资源管理 Flink深入浅出:部署模式 Flink深入 ...