C系编程语言盘点
2024-11-06 14:35:27 阿炯
C语言傲视群雄的资本还是有目共睹的,毕竟这门编程语言之所以如此霸气,是因为它是每个现代软件系统组件的创造者,业界对C语言也有着相当高的评价。开发人员用C语言制作Web服务器、数据库、操作系统、框架、编程语言、云组件和许多软件组件。尽管许多编程语言都说它们可以“取代”C语言,但这是不可能的,因为整个计算机历史都是用C语言创造的。更多计算机语言的评价可参考此处。
但资格老的语言总是不太能跟上“现代开发”的脚步。C++语言通过实现面向对象的概念、生产语言特性和通过保持C语言的性能优先设计的标准库API,为现代开发人员改进了C语言语法。也有一些C替代方案可以解决每个C开发人员面临的问题,但设计一门实用的计算机语言又是如此之难,因此对C语言的改良就就不仅是日程的事情了,而是有相当多的落地项目:D、Go、Zig和Odin是流行的C语言替代品。
在2023年2月主流的编程语言中,挑选出流行的,具有历史影响力的,按时间从上往下排序,依据类型和指导思想进行聚类,根据互相的影响使用线条指向其的相互关系,并补充了一些关键性的特征和评价,整理下图:
系统编程通常指的是开发更接近计算机硬件或操作系统层的东西,通常实现可以运行其他软件系统的软件模块。编写操作系统、应用程序框架和嵌入式系统是系统编程的好例子。系统程序员主要有两种语言类型选择:
C及其替代方案:使用小型的高性能语言,如 Go、Zig 或 Nim。
C++及其替代方案:使用有点复杂、高性能的语言,如 D、Rust、Carbon 或 Cppfront。
Better C的语言还有这两位:
C2lang/c2compiler: the c2 programming language
C3 | An evolution, not a revolution
C2lang语言自己编译安装比较顺利。C3语言是受C2启发,做了更多的功能,但感觉上C2的功能很多时候也够用了,他们最大的特点是去掉了C语言的头文件,有了包管理的功能,直接导入import 源码文件可以使用别的包中的函数了。而C3语言只提供了部分较新的操作系统的安装包,自行在Rocky Linux 9下没有编译通过,应该试LLVM的静态链接的问题,其实现也相对粗糙。
在编程世界中,C语言一直以其强大的底层控制和高效的执行性能而备受推崇。然而,随着软件开发的复杂度日益增加,我们开始追求更简洁、更智能的语言设计。这就是C2Compiler的诞生背景——一个基于C的全新编程语言,旨在提供更加现代化的编程体验,由巴斯·万登·伯格(Bas van den Berg)创立。现已经可以摆脱C++而实现自举了,其官网也提供了相当详细的文档。它保留 C 中的适用部分,同时改进那些与其 40 年历史背道而驰的部分;此外 C2 还引入了支持“链接时优化”(LTO)的集成构建系统。同时宏系统的实现也在进展中。
另外弃用的部分包括:对头文件的使用,它严重地降低了编译器的性能;复杂类型定义,在 C2 中被模块系统和统一类型语法替代。C2lang主要目标是解决 C 语言中的一些常见问题,改善开发者的使用体验。
C2Compiler是C2项目的一部分,它试图创造一种新的语言,融合了C的所有优点,并且摒弃了一些传统痛点。它的主要改进包括去除头文件、不再使用#include,采用包(package)管理,按目标编译以及引入更有逻辑性的关键字。C2Compiler是一个强大的工具,用于解析C2源代码并生成LLVM IR或ANSI C代码。其是建立在LLVM 7.0和部分Clang 7.0基础上的,这确保了其对硬件平台的良好支持和高性能优化潜力。它的解析器和ASTBuilder类的设计受到Clang相关类的启发,以实现高效且精确的语法分析。此外,项目还列出了优先级不同的待办事项,表明团队正在不断努力完善这个项目。
应用场景
适用于多种软件开发场景:
构建系统:集成的构建系统简化了大型项目中的依赖管理和构建流程。
嵌入式开发:由于其与C语言的高度兼容性,C2适合于嵌入式设备的开发。
高性能计算:利用LLVM IR进行代码优化,C2可以在高性能计算领域大展拳脚。
教育:作为教学工具,帮助学生理解现代编程语言设计理念。
项目特点
无头文件(No Header Files):告别繁琐的头文件引用,提高开发效率。
包管理(Packages):更优的模块化设计,便于代码重用和管理。
一次编译多目标(Compiled Per Target):针对不同环境一次性编译完成,减少重复工作。
逻辑性更强的关键字(Logical Keywords):如public/local替代static,使代码更易读。
内置重构工具(Refactoring Tool):方便进行大规模代码修改。
C2Tags:类似ctags的增强版,实现快速跳转到定义,提升开发体验。
安装过程简单,不论是Linux、OSX还是Windows用户都能轻松上手。通过运行c2c命令,你就可以开始尝试各种示例,探索C2语言的魅力了。C2Compiler不仅是对经典C语言的一次创新性扩展,更是面向未来编程时代的一个重要里程碑。
主流编译器厂商在C标准上的一些差异
对于软件工程师来说,C 语言可以说是最最最主要的编程语言,然而在派生标准上有 Linux GNU C 和 ANSI C 这两者却存在着一定的差异,使用不当很容易造成语法错误。
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!\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'
但资格老的语言总是不太能跟上“现代开发”的脚步。C++语言通过实现面向对象的概念、生产语言特性和通过保持C语言的性能优先设计的标准库API,为现代开发人员改进了C语言语法。也有一些C替代方案可以解决每个C开发人员面临的问题,但设计一门实用的计算机语言又是如此之难,因此对C语言的改良就就不仅是日程的事情了,而是有相当多的落地项目:D、Go、Zig和Odin是流行的C语言替代品。
在2023年2月主流的编程语言中,挑选出流行的,具有历史影响力的,按时间从上往下排序,依据类型和指导思想进行聚类,根据互相的影响使用线条指向其的相互关系,并补充了一些关键性的特征和评价,整理下图:
系统编程通常指的是开发更接近计算机硬件或操作系统层的东西,通常实现可以运行其他软件系统的软件模块。编写操作系统、应用程序框架和嵌入式系统是系统编程的好例子。系统程序员主要有两种语言类型选择:
C及其替代方案:使用小型的高性能语言,如 Go、Zig 或 Nim。
C++及其替代方案:使用有点复杂、高性能的语言,如 D、Rust、Carbon 或 Cppfront。
Better C的语言还有这两位:
C2lang/c2compiler: the c2 programming language
C3 | An evolution, not a revolution
C2lang语言自己编译安装比较顺利。C3语言是受C2启发,做了更多的功能,但感觉上C2的功能很多时候也够用了,他们最大的特点是去掉了C语言的头文件,有了包管理的功能,直接导入import 源码文件可以使用别的包中的函数了。而C3语言只提供了部分较新的操作系统的安装包,自行在Rocky Linux 9下没有编译通过,应该试LLVM的静态链接的问题,其实现也相对粗糙。
在编程世界中,C语言一直以其强大的底层控制和高效的执行性能而备受推崇。然而,随着软件开发的复杂度日益增加,我们开始追求更简洁、更智能的语言设计。这就是C2Compiler的诞生背景——一个基于C的全新编程语言,旨在提供更加现代化的编程体验,由巴斯·万登·伯格(Bas van den Berg)创立。现已经可以摆脱C++而实现自举了,其官网也提供了相当详细的文档。它保留 C 中的适用部分,同时改进那些与其 40 年历史背道而驰的部分;此外 C2 还引入了支持“链接时优化”(LTO)的集成构建系统。同时宏系统的实现也在进展中。
另外弃用的部分包括:对头文件的使用,它严重地降低了编译器的性能;复杂类型定义,在 C2 中被模块系统和统一类型语法替代。C2lang主要目标是解决 C 语言中的一些常见问题,改善开发者的使用体验。
C2Compiler是C2项目的一部分,它试图创造一种新的语言,融合了C的所有优点,并且摒弃了一些传统痛点。它的主要改进包括去除头文件、不再使用#include,采用包(package)管理,按目标编译以及引入更有逻辑性的关键字。C2Compiler是一个强大的工具,用于解析C2源代码并生成LLVM IR或ANSI C代码。其是建立在LLVM 7.0和部分Clang 7.0基础上的,这确保了其对硬件平台的良好支持和高性能优化潜力。它的解析器和ASTBuilder类的设计受到Clang相关类的启发,以实现高效且精确的语法分析。此外,项目还列出了优先级不同的待办事项,表明团队正在不断努力完善这个项目。
应用场景
适用于多种软件开发场景:
构建系统:集成的构建系统简化了大型项目中的依赖管理和构建流程。
嵌入式开发:由于其与C语言的高度兼容性,C2适合于嵌入式设备的开发。
高性能计算:利用LLVM IR进行代码优化,C2可以在高性能计算领域大展拳脚。
教育:作为教学工具,帮助学生理解现代编程语言设计理念。
项目特点
无头文件(No Header Files):告别繁琐的头文件引用,提高开发效率。
包管理(Packages):更优的模块化设计,便于代码重用和管理。
一次编译多目标(Compiled Per Target):针对不同环境一次性编译完成,减少重复工作。
逻辑性更强的关键字(Logical Keywords):如public/local替代static,使代码更易读。
内置重构工具(Refactoring Tool):方便进行大规模代码修改。
C2Tags:类似ctags的增强版,实现快速跳转到定义,提升开发体验。
安装过程简单,不论是Linux、OSX还是Windows用户都能轻松上手。通过运行c2c命令,你就可以开始尝试各种示例,探索C2语言的魅力了。C2Compiler不仅是对经典C语言的一次创新性扩展,更是面向未来编程时代的一个重要里程碑。
主流编译器厂商在C标准上的一些差异
对于软件工程师来说,C 语言可以说是最最最主要的编程语言,然而在派生标准上有 Linux GNU C 和 ANSI C 这两者却存在着一定的差异,使用不当很容易造成语法错误。
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!\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'