目录
1. 零长度和变量长度数组" >1. 零长度和变量长度数组
2.case范围" >2.case范围
3.语句表达式" >3.语句表达式
4.typeof关键字" >4.typeof关键字
5.可变参数宏" >5.可变参数宏
6.标号元素" >6.标号元素
7.当前函数名" >7.当前函数名
8.特殊属性声明" >8.特殊属性声明
9.内建函数" >9.内建函数
首页 系统教程 操作系统 答疑解惑 | Linux GNU C 与 ANSI C 的区别

答疑解惑 | Linux GNU C 与 ANSI C 的区别

Feb 05, 2024 am 10:48 AM
linux linux教程 linux系统 linux命令 外壳脚本 嵌入式linux 良好的许可证 linux入门 linux学习

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

答疑解惑 | Linux GNU C 与 ANSI 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 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 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); _ _xfloat fa, fb, minf; 
mini = min_t(int, ia, ib); 
minf = min_t(float, fa, fb);
登录后复制

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

#define min(x,y) ((x) 
登录后复制

代码min(++ia,++ib)会展开为((++ia)

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 
登录后复制

我们不需要像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!\n")
登录后复制

会被正确地扩展为:

printk("success!\n")
登录后复制

而不是:

printk("success!\n",)
登录后复制

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'
登录后复制

以上是答疑解惑 | Linux GNU C 与 ANSI C 的区别的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

vscode需要什么电脑配置 vscode需要什么电脑配置 Apr 15, 2025 pm 09:48 PM

VS Code 系统要求:操作系统:Windows 10 及以上、macOS 10.12 及以上、Linux 发行版处理器:最低 1.6 GHz,推荐 2.0 GHz 及以上内存:最低 512 MB,推荐 4 GB 及以上存储空间:最低 250 MB,推荐 1 GB 及以上其他要求:稳定网络连接,Xorg/Wayland(Linux)

Linux体系结构:揭示5个基本组件 Linux体系结构:揭示5个基本组件 Apr 20, 2025 am 12:04 AM

Linux系统的五个基本组件是:1.内核,2.系统库,3.系统实用程序,4.图形用户界面,5.应用程序。内核管理硬件资源,系统库提供预编译函数,系统实用程序用于系统管理,GUI提供可视化交互,应用程序利用这些组件实现功能。

notepad怎么运行java代码 notepad怎么运行java代码 Apr 16, 2025 pm 07:39 PM

虽然 Notepad 无法直接运行 Java 代码,但可以通过借助其他工具实现:使用命令行编译器 (javac) 编译代码,生成字节码文件 (filename.class)。使用 Java 解释器 (java) 解释字节码,执行代码并输出结果。

vscode 无法安装扩展 vscode 无法安装扩展 Apr 15, 2025 pm 07:18 PM

VS Code扩展安装失败的原因可能包括:网络不稳定、权限不足、系统兼容性问题、VS Code版本过旧、杀毒软件或防火墙干扰。通过检查网络连接、权限、日志文件、更新VS Code、禁用安全软件以及重启VS Code或计算机,可以逐步排查和解决问题。

git怎么查看仓库地址 git怎么查看仓库地址 Apr 17, 2025 pm 01:54 PM

要查看 Git 仓库地址,请执行以下步骤:1. 打开命令行并导航到仓库目录;2. 运行 "git remote -v" 命令;3. 查看输出中的仓库名称及其相应的地址。

vscode终端使用教程 vscode终端使用教程 Apr 15, 2025 pm 10:09 PM

vscode 内置终端是一个开发工具,允许在编辑器内运行命令和脚本,以简化开发流程。如何使用 vscode 终端:通过快捷键 (Ctrl/Cmd ) 打开终端。输入命令或运行脚本。使用热键 (如 Ctrl L 清除终端)。更改工作目录 (如 cd 命令)。高级功能包括调试模式、代码片段自动补全和交互式命令历史。

vscode在哪写代码 vscode在哪写代码 Apr 15, 2025 pm 09:54 PM

在 Visual Studio Code(VSCode)中编写代码简单易行,只需安装 VSCode、创建项目、选择语言、创建文件、编写代码、保存并运行即可。VSCode 的优点包括跨平台、免费开源、强大功能、扩展丰富,以及轻量快速。

vscode 可以用于 mac 吗 vscode 可以用于 mac 吗 Apr 15, 2025 pm 07:36 PM

VS Code 可以在 Mac 上使用。它具有强大的扩展功能、Git 集成、终端和调试器,同时还提供了丰富的设置选项。但是,对于特别大型项目或专业性较强的开发,VS Code 可能会有性能或功能限制。

See all articles