0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

探究Linux GNU C与ANSI C之间的区别

奈因PCB电路板设计 来源:嵌入式Linux系统开发 作者:嵌入式Linux系统开 2021-07-26 16:54 次阅读

Linux 上可用的 C 编译器是 GNU C 编译器,它建立在自由软件基金会的编程许可证的基础上,因此可以自由发布。GNU C对标准C进行一系列扩展,以增强标准C的功能。

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

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

struct var_data {

int len;

char data[0];

};

char data[0]仅仅意味着程序中通过var_data结构体实例的data[index]成员可以访问len之后的第index个地址,它并 没有为data[]数组分配内存,因此sizeof(struct var_data)=sizeof(int)。

假设struct var_data的数据域就保存在struct var_data紧接着的内存区域中,则通过如下代码可以遍历这些数据:

struct var_data s;

。。。

for (i = 0; i 《 s.len; i++)

printf(“%02x”, s.data[i]);

GNU C中也可以使用1个变量定义数组,例如如下代码中定义的“double x[n]”:

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

{

int i, n = argc;

double x[n];

for (i = 0; i 《 n; i++)

x[i] = i;

return 0;

}

2.case范围

GNU C支持case x…y这样的语法,区间[x,y]中的数都会满足这个case的条件,请看下面的代码:

switch (ch) {

case ‘0’。。。 ‘9’: c -= ‘0’;

break;

case ‘a’。。。 ‘f’: c -= ‘a’ - 10;

break;

case ‘A’。。。 ‘F’: c -= ‘A’ - 10;

break;

}

代码中的case‘0’。。。‘9’等价于标准C中的:

case ‘0’: case ‘1’: case ‘2’: case ‘3’: case ‘4’:

case ‘5’: case ‘6’: case ‘7’: case ‘8’: case ‘9’:

3.语句表达式

GNU C把包含在括号中的复合语句看成是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地 方。我们可以在语句表达式中使用原本只能在复合语句中使用的循环、局部变量等,例如:

#define min_t(type,x,y)

( { type _ _x =(x);type _ _y = (y); _ _x《_ _y _ _x: _ _y; })

int ia, ib, mini;

float fa, fb, minf;

mini = min_t(int, ia, ib);

minf = min_t(float, fa, fb);

因为重新定义了__xx和__y这两个局部变量,所以用上述方式定义的宏将不会有副作用。在标准C中,对应的如 下宏则会产生副作用:

#define min(x,y) ((x) 《 (y) (x) : (y))

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

4.typeof关键字

typeof(x)语句可以获得x的类型,因此,可以借助typeof重新定义min这个宏:

#define min(x,y) ({ const typeof(x) _x = (x);

const typeof(y) _y = (y);

(void) (&_x == &_y);

_x 《 _y _x : _y; })

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

5.可变参数宏

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

int printf( const char *format [, argument]。。。 );

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

#define pr_debug(fmt,arg.。。)

printk(fmt,##arg)

这里arg表示其余的参数,可以有零个或多个参数,这些参数以及参数之间的逗号构成arg的值,在宏扩展时替换 arg,如下列代码:

pr_debug(“%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

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10987

    浏览量

    206723

原文标题:Linux GNU C 与 ANSI C 有什么区别?

文章出处:【微信号:pcbgood,微信公众号:奈因PCB电路板设计】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    在STM32中,通信串口USART与I2C之间有啥原理上的区别?二者之间又有什么联系?

    请问一下,在STM32中,通信串口USART与I2C之间有啥原理上的区别?二者之间又有什么联系?对于所有的通信之间,又存在什么样的关联?
    发表于 03-25 07:27

    求助,如何将C++代码从GNU移植到Tasking编译器?

    需要解决的障碍。 我恳请你帮助我们解决这个问题。 问题 :当前代码是使用 GNU 编译器编译的,代码按定义运行。 但是,我们希望让它在 Aurix TC399 开发套件上运行。 在此过程中,我
    发表于 01-31 07:29

    ubuntu和linux区别

    Ubuntu和Linux是两个相关但不完全相同的概念,它们之间有着一些区别。在开始深入讨论Ubuntu和Linux之间
    的头像 发表于 11-27 17:06 871次阅读

    linux属于什么操作系统

    Linux属于一种类UNIX操作系统。Linux,全称GNU/Linux,是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和Unix的多用户、多任务、支持多线程和多C
    的头像 发表于 11-08 11:01 1725次阅读

    LinuxGNU time命令的使用

    更强大的功能 GNU time 命令提供了更强大的功能: 更详细的统计信息 更丰富的格式输出 支持保存统计数据到文件 下边我们来学习写 GNU time 的使用 1. 最简单的用法 root
    的头像 发表于 10-09 17:53 436次阅读

    LinuxC语言编程入门教程

    u3000本文是LinuxC 语言编程入门教程。主要介绍了Linux 的发展与特点、C语言的基础知识、Linux 程序设计基础知识及其下
    发表于 09-22 06:56

    如何为Arm编译Cc++代码

    Studio或Keil MDK IDE环境中。 Linux编译常见的编程语言在Arm上得到了很好的支持,大多数开源工具都可以在Linux发行版提供的包中使用。用于C++、C和Fort
    发表于 08-02 17:28

    浅析Linux内核中常用的C语言技巧

    Linux内核采用的是GCC编译器,GCC编译器除了支持ANSI C,还支持GNU C。在Linux内核中,许多地方都使用了GNU C语言的
    发表于 06-25 10:46 286次阅读

    GNU C语言的特性

    Linux内核采用的是GCC编译器,GCC编译器除了支持ANSI C,还支持GNU C。在Linux内核中,许多地方都使用了GNU C语言的
    的头像 发表于 06-22 10:05 473次阅读

    什么是 GNU/Linux

    Linus本人曾经争辩说,将Linux称为GNU/Linux简直是荒谬的。
    发表于 06-13 09:14 2606次阅读
    什么是 <b class='flag-5'>GNU</b>/<b class='flag-5'>Linux</b>?

    揭秘GNU/Linux:为什么几乎没人敢这么称呼它?

    你对Linux感兴趣并读了一些博文。在其中,你遇到了一个有趣的名字:GNU/Linux。这些字母代表着什么意思,为什么人们总是将它们与Linux联系在一起呢?
    发表于 06-06 14:18 341次阅读
    揭秘<b class='flag-5'>GNU</b>/<b class='flag-5'>Linux</b>:为什么几乎没人敢这么称呼它?

    EC2实例gcc-aarch64-linux-gnu和g++-aarch64-linux-gnu不支持goldvip图像吗?

    “git-core” 包gcc-aarch64-linux-gnu 不可用,但被另一个包引用。 这可能意味着该软件包已丢失、已过时或 只能从其他来源获得 包 g++-aarch64-linux-gnu 不可用
    发表于 06-05 08:22

    Linux内核中常用的C语言技巧有哪些

    Linux内核采用的是GCC编译器,GCC编译器除了支持ANSI C,还支持GNU C。在Linux内核中,许多地方都使用了GNU C语言的
    的头像 发表于 05-12 14:45 402次阅读

    I2C通信间隔的版本之间有什么区别吗?

    初始化PN71506版芯片和PN71501版芯片时代码相同 确认PN71501版芯片的I2C通信READ TIME时间稍长。 (06版本读30ms,01版本30ms不读,导致同段代码出错,改READ TIME为40ms,01版本读)。 关于 I2C 通信间隔的版本
    发表于 05-06 07:19

    NodeMCU和Arduino/C在ESP上的区别是什么?

    搜索,让 ESP 直接运行自定义 C 代码似乎很常见。这让我想知道这两种方法之间区别。显然每个人都有自己的优点和缺点,否则它们不会并存。有人能告诉我运行 NodeMCU 和纯 C
    发表于 04-27 06:55