探索Linux GNU C和ANSI C的区别

2021-07-26 17:29 来源:电子说

Linux上可用的C编译器是GNU C编译器,基于自由软件基金会的编程许可,所以可以自由分发。GNU C对标准C进行了一系列扩展,以增强标准C的功能.

1.零长度和可变长度数组

GNU C允许使用零长度数组,这在定义可变长度对象的头结构时非常有用。例如:

struct var_data {

int len

char数据[0];

};

Chardata [0]只意味着程序中var_data结构实例的data[index]]成员可以访问len之后的索引地址,它不为data []数组分配内存,所以sizeof(struct var _ data)=sizeof(int)。

假设struct var_data的数据字段存储在紧随struct var_data之后的内存区域中,这些数据可以由以下代码遍历:

struct var _ data s;

for(I=0;I《s . len;(一)

printf("x ",s . data[I]);

在GNU C中,一个数组也可以用一个变量来定义,比如下面代码中定义的“double x [n]”:

int main (int argc,char *argv[])

{

int i,n=argc

double x[n];

for(I=0;I《n;(一)

x[I]=I;

返回0;

}

2 .案件范围

GNU C支持大小写x…y的语法,区间[x,y]内的数字会满足这种情况的条件。请参见以下代码:

开关(ch)

大小写“0”.“9”:c-=“0”;

打破;

案例a .' f ':c-=' a '-10;

打破;

案例' A ' .' f ':c-=' A '-10;

打破;

}

代码中的大小写“0”为0。' 9 '相当于标准c:

案例“0”:案例“1”:案例“2”:案例“3”:案例“4”:

案例“5”:案例“6”:案例“7”:案例“8”:案例“9”:

3.语句表达式

GNU C将括号中包含的复合语句视为一个表达式,称为语句表达式,可以出现在任何允许表达式的地方。我们可以在只能在复合语句中使用的语句表达式中使用循环和局部变量,例如:

#定义min_t(类型,x,y)

({ type _ _ x=(x);type _ _ y=(y);_ _ x “_ _ y _ _ x:_ _ y;})

int ia,ib,mini

浮动fa、fb、minf

mini=min_t(int,ia,IB);

minf=min_t(float,fa,FB);

因为局部变量__xx和__y被重新定义,以上述方式定义的宏不会有副作用。在标准c中,相应的宏如下面这样会有副作用:

#定义最小值(x,y)((x)((y)(x):(y))

代码min(ia,ib)扩展为((ia)”(IB)(ia):(IB)),传入宏的“参数”增加两次。

4 .关键字的类型

x的类型可以通过typeof(x)语句获得,因此可以通过类型重新定义宏min:

#定义min(x,y)({ const type of(x)_ x=(x));

const type of(y)_ y=(y);

(void)(_ x==_ y);

_ x “_ y _ x:_ y;})

我们不需要像宏min_t(type,x,y)那样传入type,因为type可以通过typeof(x)和typeof(y)获得。替换代码行(void)(_x==_y)的作用是检查_x和_y的类型是否一致。

5.可变参数宏

标准C支持可变参数函数,这意味着函数的参数不是固定的。例如,printf()函数的原型是:

int printf(const char *format [,参数]).);

在GNU C中,宏也可以接受可变数量的参数,例如:

#定义pr_debug(fmt,arg。)

printk(fmt,#arg)

这里,arg代表其余的参数,可以有零个或多个参数。这些参数和参数之间的逗号构成了arg的值,当宏展开时,该值将被替换,如以下代码所示:

pr_d

ebug(“%s:%d”,filename,line)

会被扩展为:

printk(“%s:%d”, filename, line)

使用“##”是为了处理arg不代表任何参数的情况,这时候,前面的逗号就变得多余了。使用“##”之后,GNU C预 处理器会丢弃前面的逗号,这样,下列代码:

pr_debug(“success!

”)

会被正确地扩展为:

printk(“success!

”)

而不是:

printk(“success!

”,)

6.标号元素

标准C要求数组或结构体的初始化值必须以固定的顺序出现,在GNU C中,通过指定索引或结构体成员名,允许 初始化值以任意顺序出现。

指定数组索引的方法是在初始化值前添加“[INDEX]=”,当然也可以用“[FIRST.。.LAST]=”的形式指定一个范围。例如,下面的代码定义了一个数组,并把其中的所有元素赋值为0:

unsigned char data[MAX] = { [0 。。。 MAX-1] = 0 };

下面的代码借助结构体成员名初始化结构体:

struct file_operations ext2_file_operations = {

llseek: generic_file_llseek,

read: generic_file_read,

write: generic_file_write,

ioctl: ext2_ioctl,

mmap: generic_file_mmap,

open: generic_file_open,

release: ext2_release_file,

fsync: ext2_sync_file,

};

但是,Linux 2.6推荐类似的代码应该尽量采用标准C的方式:

struct file_operations ext2_file_operations = {

.llseek = generic_file_llseek,

.read = generic_file_read,

.write = generic_file_write,

.aio_read = generic_file_aio_read,

.aio_write = generic_file_aio_write,

.ioct = ext2_ioctl,

.mmap = generic_file_mmap,

.open = generic_file_open,

.release = ext2_release_file,

.fsync = ext2_sync_file,

.readv = generic_file_readv,

.writev = generic_file_writev,

.sendfile = generic_file_sendfile,

};

7.当前函数名

GNU C预定义了两个标识符保存当前函数的名字,__FUNCTION__保存函数在源码中的名字,__PRETTY_FUNCTION__保存带语言特色的名字。在C函数中,这两个名字是相同的。

void example()

{

printf(“This is function:%s”, __FUNCTION__);

}

代码中的__FUNCTION__意味着字符串“example”。C99已经支持__func__宏,因此建议在Linux编程中不再使用__FUNCTION__,而转而使用__func__:

void example(void)

{

printf(“This is function:%s”, __func__);

}

8.特殊属性声明

GNU C允许声明函数、变量和类型的特殊属性,以便手动优化代码和定制代码检查的方法。要指定一个声明的 属性,只需要在声明后添加__attribute__((ATTRIBUTE))。其中ATTRIBUTE为属性说明,如果存在多个属 性,则以逗号分隔。GNU C支持noreturn、format、section、aligned、packed等十多个属性。

noreturn属性作用于函数,表示该函数从不返回。这会让编译器优化代码,并消除不必要的警告信息。例如:

# define ATTRIB_NORET __attribute__((noreturn)) 。。。。 asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;

format属性也用于函数,表示该函数使用printf、scanf或strftime风格的参数,指定format属性可以让编译器根据格 式串检查参数类型。例如:

asmlinkage int printk(const char * fmt, 。。。) __attribute__ ((format (printf, 1, 2)));

上述代码中的第1个参数是格式串,从第2个参数开始都会根据printf()函数的格式串规则检查参数。

unused属性作用于函数和变量,表示该函数或变量可能不会用到,这个属性可以避免编译器产生警告信息。

aligned属性用于变量、结构体或联合体,指定变量、结构体或联合体的对齐方式,以字节为单位,例如:

struct example_struct {

char a;

int b;

long c;

} __attribute__((aligned(4)));

表示该结构类型的变量以4字节对齐。

packed属性作用于变量和类型,用于变量或结构体成员时表示使用最小可能的对齐,用于枚举、结构体或联合体类型时表示该类型使用最小的内存。例如:

struct example_struct {

char a;

int b;

long c __attribute__((packed));

};

编译器对结构体成员及变量对齐的目的是为了更快地访问结构体成员及变量占据的内存。例如,对 于一个32位的整型变量,若以4字节方式存放(即低两位地址为00),则CPU在一个总线周期内就可以读取32 位;否则,CPU需要两个总线周期才能读取32位。

9.内建函数

GNU C提供了大量内建函数,其中大部分是标准C库函数的GNU C编译器内建版本,例如memcpy()等,它们与对应的标准C库函数功能相同。

不属于库函数的其他内建函数的命名通常以__builtin开始,如下所示。

内建函数__builtin_return_address(LEVEL)返回当前函数或其调用者的返回地址,参数LEVEL指定调用栈的级数,如0表示当前函数的返回地址,1表示当前函数的调用者的返回地址。

内建函数__builtin_constant_p(EXP)用于判断一个值是否为编译时常数,如果参数EXP的值是常数,函数返回1,否则返回0。例如,下面的代码可检测第1个参数是否为编译时常数以确定采用参数版本还是非参数版本:

#define test_bit(nr,addr)

(__builtin_constant_p(nr)

constant_test_bit((nr),(addr)) :

variable_test_bit((nr),(addr)))

内建函数__builtin_expect(EXP,C)用于为编译器提供分支预测信息,其返回值是整数表达式EXP的值,C的 值必须是编译时常数。

Linux内核编程时常用的likely()和unlikely()底层调用的likely_notrace()、unlikely_notrace()就是基于 __builtin_expect(EXP,C)实现的。

#define likely_notrace(x) __builtin_expect(!!(x), 1) #define unlikely_notrace(x) __builtin_expect(!!(x), 0)

若代码中出现分支,则即可能中断流水线,我们可以通过likely()和unlikely()暗示分支容易成立还是不容易 成立,例如:

if (likely(!IN_DEV_ROUTE_LOCALNET(in_dev)))

if (ipv4_is_loopback(saddr))

goto e_inval;

在使用gcc编译C程序的时候,如果使用“-ansi–pedantic”编译选项,则会告诉编译器不使用GNU扩展语法。例如对 于如下C程序test.c:

struct var_data {

int len;

char data[0];

};

struct var_data a;

直接编译可以通过:

gcc -c test.c

如果使用“-ansi–pedantic”编译选项,编译会报警:

gcc -ansi -pedantic -c test.c

test.c:3: warning: ISO C forbids zero-size array ‘data’

编辑:jq

延伸 · 阅读