GCC 对C语言的扩展

Linux内核必须使用GNU的GCC编译器来编译,而GCC提供了很多的C语言扩展,这些扩展对优化、目标代码布局、更安全的检查等提供了很强的支持。因此,内核代码所使用的C语法并不完全符合ANSI
C标准,实际上,只要有可能,内核开发者总是要用到GCC提供的C语言扩展部分。所以特意找了几个常用的特性总结下。

1、语句内嵌表达式(statement-embedded expression)

在gnu c 中,用括号将复合语句括起来也形成了表达式。他允许你在一个表达式内使用循环,跳转和局部变量。

一个复合语句是用大括号{}括起来的一组语句。在包含语句的表达式这种结构中,再用括号( )将大括号括起来

例如:

({ int y = foo (); int z;

if (y > 0) z = y;

else z = - y;

z; }

)

就是一个合法表达式,用于计算foo( )函数返回值的绝对值。

在上面的复合语句中,最后的一句必须是一个以分号结尾的表达式。这个表达式代表了整个结构的值。

如果你在大括号里的最后一句用的是其他的语句,则整个结构的返回类型为void,即没有合法的返回值。

这种特性使得宏定义变得更加安全(因为每个操作数都只被计算一次,例如++运算)。例如计算最大值通常在c语言中被定义为这样的宏:

#define max(a,b) ((a) > (b) ? (a) : (b))

但是其中的a和b可能会被计算两次,如果操作数带有副作用,则会产生错误的结果。

在gnu c中,如果你知道了操作数的类型(假设为int),你可以这样安全的定义宏:

#define maxint(a,b) \

({int _a = (a), _b = (b); _a > _b ? _a : _b; })

内核中做法:

#define min_t(type,x,y) \

({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })

#define max_t(type,x,y) \

({ type __x = (x); type __y = (y); __x > __y ? __x: __y; })

使用语句表达式只计算参数一次,避免了可能的错误。

语句内嵌在常量表达式(例如枚举类型),位域尺寸或静态变量初始化中是不允许的。

如果你不知道操作数的类型,你也可以使用typeof来获得类型。

典型用法用下单链表pop操作:

#define _$slist_pop(H,L)\

({\

typeof((H)->L) _p$ = (H)->L;\

if( _p$){ (H)->L = _p$->next; _p$->next = NULL;} _p$;\

})

使用 typeof 获得类型、最后一个 _p$ 用于返回值。

2、可变参数宏 __VA_ARGS__ 常用: ##__VA_ARGS__

举例如下:

#define err_log(fmt, ...) fprintf (stderr, fmt, __VA_ARGS__)

其中的"…"表示可变参数,实际调用时,它们会替代宏体里的__VA_ARGS__。

但在fprintf的参数列表中最后的逗号后面没有参数。在编译时就会报错。

此时可如此定义:

#define err_log(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)

"##"主要针对参数为空的情况。既然称为可变参数,那传递空参数也是可以的。如果没有使用"##",传递空参数时,比如:

err_log("A null message");

宏展开后,其中的字符串后面会多个多余的逗号,而"##"则会使预处理器去掉这个多余的逗号。

还有如下的宏方便用于调试:

__FILE__ 宏在预编译时会替换成当前的源文件名

__LINE__宏在预编译时会替换成当前的行号

__FUNCTION__宏在预编译时会替换成当前的函数名称

3、GCC 中零长数组

GCC 中允许使用零长数组,把它作为结构体的最后一个元素非常有用.

struct line {

int length;

char contents[0];

};

struct line *thisline = (struct line *) malloc (sizeof (struct line) + this_length);

thisline->length = this_length;

零长数组在有固定头部的可变对象上非常适用,我们可以根据对象的大小动态地去分配结构体的大小。

作为零长度数组的原始实现的神奇之处,sizeof被赋值为0。sizeof (struct line) = 4B

4、标号元素

在标准C里,数组或结构变量的初始化值必须以固定的顺序出现,而在GCC中,通过指定索引或结构域名,

则允许初始化值以任意顺序出现。

指定数组索引的方法是在初始化值前写"[INDEX] =",还可以使用"[FIRST ... LAST] ="的形式指定一个范围

int platform_intr_list[ACPI_MAX_PLATFORM_INTERRUPTS] = {

[0 ... ACPI_MAX_PLATFORM_INTERRUPTS - 1] = -1

};

将数组platform_intr_list的任何元素都初始化为-1

对于结构初始化,比如:

const struct file_operations ext2_file_operations = {

.llseek     = generic_file_llseek,

.read       = do_sync_read,

.write      = do_sync_write,

}

将结构ext2_file_operations的元素llseek初始化为generic_file_llseek,当结构体的定义变化导致元素的偏移位置改变时,仍然可以确保已知元素的正确性。

5、特殊属性(__attribute__)

GCC允许声明函数、变量和类型的特殊属性,以便指示编译器进行特定方面的优化和更仔细的代码检查。使用方式为在声明后加上:

attribute__ (( ATTRIBUTE ))

其中ATTRIBUTE是属性的说明,多个说明之间以逗号分隔.

//=========变参调用的编译检查宏==========

#ifndef _AS_PRINTF

#if defined(__GNUC__)

# define _$PRINTF(m,n) __attribute__ ((__format__ (__printf__, (m), (n))))

# define _$SCANF(m,n) __attribute__ ((__format__ (__scanf__, (m), (n))))

#else

# define _$PRINTF(m,n)

# define _$SCANF(m,n)

#endif

#endif

=========================================================

顺便对比了一下逗号表达式与语句内嵌表达式的区别:

逗号运算符,优先级别最低,逗号表达式的运算规则是从左向右依此计算,把最后一个表达式的值作为整个表达式的值.

举例说明:

main()

{

int x,y,z;

x=y=1;

z=x++,y++,++y;

printf("%d,%d,%d\n",x,y,z);

}

记住逗号运算符优先级最低、所以等价:(z=x++),y++,++y; 答案: x(2),y(3),z(1)

(a = 3,b = 5,b+ = a,c = b* 5),求逗号表达式的值? 40

func(rec1,rec2+rec3,(rec4,rec5));该函数调用语句中,含有的实参个数是

C语言中规定,函数调用时实参与实参之间是用逗号隔开的,其中第一个实参是rec1,第二个实参是rec2+rec3,

第三个实参是(rec4,rec5),这里的第三个实参就是一个逗号表达式,根据逗号表达式的运算规则,

第三个实参的值应该等于rec5的值。

fun(a+b,(x,y),fun(n+k,d,(a,b))); 在此函数调用语句中实参的个数是 3

时间: 2024-11-08 12:37:34

GCC 对C语言的扩展的相关文章

study Mvc step by step (三)C#语言特性扩展方法

C#3.0之后推出了扩展方法.我们通常看到的方法都是和声明它的类相关联.扩展方法特性扩展这个边界,允许编写的方法和声明它的类之外的类关联. 要想知道可以如何使用这个特性,请看下面的代码.它包含类MyPerson.该类存贮了3个double类型的值,并含有一个构造函数和一个名称为sum的方法,该方法返回3个存储值得和. using System; using System.Collections.Generic; using System.Linq; using System.Text; usin

gcc编译c语言

摘自<Linux程序设计>第四版,人民邮电出版社 c语言程序的编译与调试<<使用gcc编译,gdb调试>> 程序编译过程:词法分析-->语法分析-->中间代码生成-->代码优化-->目标代码生成gcc编译器:预处理(preprocessing)-->编译(compilation)-->汇编(assembly)-->连接(link) 文件后缀名说明:.c:c语言代码.a:由目标文件构成的库文件.C,.cc,.cpp:C++代码.h

第01节:Linux 内核中的 C 语言语法扩展

1.1 Linux 内核驱动中的奇怪语法 大家在看一些 GNU 开源软件,或者阅读 Linux 内核.驱动源码时会发现,在 Linux 内核源码中,有大量的 C 程序看起来"怪怪的".说它是C语言吧,貌似又跟教材中的写法不太一样:说它不是 C 语言呢,但是这些程序确确实实是在一个 C 文件中.此时,你肯定怀疑你看到的是一个"假的 C 语言"! 比如,下面的宏定义: #define mult_frac(x, numer, denom)( { typeof(x) quo

GCC在C语言中内嵌汇编 asm __volatile__ 【转】

转自:http://blog.csdn.net/pbymw8iwm/article/details/8227839 在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C 变量,你只要告诉程序中C语言表达式与汇编指令操作数之间的对应关系即可, GCC会自动插入代码完成必要的操作. 1.简单的内嵌汇编 例: __asm__ __volatile__("hlt"); "__asm__"表示后面的

1 C语言 gcc 介绍 C 语言编译 main接受参数

1         第一个c语言的hello world 1.1      include头文件包含 头文件包含,写法#include<文件名>, 1.2      main函数 这个就是C语言程序的入口,所有的C程序都是从main开始执行,一个C的源程序必须有一个main函数,也只能有一个main函数 1.3      注释 //注释一行 /* */代表块注释,可以注释多行代码 1.4      {}括号和代码块 代表一个代码单元 1.5      声明 C语言规定,所有的变量和函数必须先声

windows下gcc开发c语言环境配置

工具准备:(配置windows) 1.安装qt5 2.在window系统的path路径中增加化境变量: C:\Qt\Qt5.6.2\5.6\mingw49_32\bin C:\Qt\Qt5.6.2\Tools\mingw492_32\bin 3.打开命令窗口,出入gcc –v,可以查看版本信息 4.打开qt生成一个空的项目,编译后程序可以运行,表示qt配置成功. 工具准备:(配置linux) linux自带gcc编译器. Helloworld程序的结构: #include "stdio.h&qu

GCC在C语言中内嵌汇编 asm __volatile__

转自 http://blog.csdn.net/pbymw8iwm/article/details/8227839 在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C 变量,你只要告诉程序中C语言表达式与汇编指令操作数之间的对应关系即可, GCC会自动插入代码完成必要的操作. 1.简单的内嵌汇编例: __asm__ __volatile__("hlt"); "__asm__"表示后面的代

利用boost.python 通过c++语言来扩展python (python.boost)

python语言的优良性就不多说了,我想提下如何使用boost.python,通过boost.python既可以将python转移到C++上,通过Python库,也可以通过C++来扩展python,下面主要介绍使用boost.python来扩展python的功能,第一次用boost.python,倒腾了半天才搞定: 首先列出我的测试环境: 我用的是VS2010,python2.7,我用VS2010创建了一个windows DLL的项目,项目名称为pylib,在DLL main中加入如下代码: /

用gcc编译c语言程序以及其编译过程

对于初学c语言编程的我们来说,学会如何使用gcc编译器工具,对理解c语言的执行过程,加深对c语言的理解很重要!!! 1.预编译 --> 2.编译 --> 3.汇编 --> 4.链接----------------------------------------------------------------------------- 0.编写c代码,并输入以下如图代码,生成c文件hello.c. ----------------------------------------------