大家好!我是大聪明-PLUS!
GNU 编译器集合 (GCC) 提供了 ISO 标准 C 中没有的几种语言功能。
在我撰写本文时,GCC 拥有超过60个不同的拓展,它们可以改变 C 语言(以及 C++)的行为或添加功能。其中一些扩展非常有趣,另一些则有点令人困惑,还有一些则有些危险!
了解这些 GCC 扩展非常重要,因为其中许多扩展被多个免费和开源项目使用,包括 Linux 内核。
让我们来看看其中的一些吧?
嵌套函数
GCC 允许声明嵌套函数(在另一个函数内部定义的函数)。
嵌套函数的名称将是其定义块的本地名称,并且可以访问其定义点可见的包含函数的所有变量。
|----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| ` 1 2 3 4 5 6 7 8 9 10 `
| `#include int main(int argc, const char *argv[]) { void p (void) { printf("Inside a nested function!n"); } p(); return 0; }`
|
`$ gcc nested.c -Wall -Werror -o nested
$ ./nested
Inside nested function!`
typeof 关键字
我们可以使用typedef关键字来引用表达式的类型,从而可以在 C 中进行泛型编程!
例如,下面的*max(a,b)*宏可对任何算术类型进行操作:
|-------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 `
| `#include #define max(a,b) ({ typeof (a) _a = (a); typeof (b) _b = (b); _a > _b ? _a : _b; }) int main(int argc, const char *argv[]) { int x = 1024, y = 4096; char a = 10, b = 20; float j = 1.0, k = 2.0; printf("char max is %dn", max(a,b)); printf("int max is %dn", max(x,y)); printf("float max is %fn", max(j,k)); return 0; }`
|
`$ gcc typeof.c -Wall -Werror -o typeof
$ ./typeof
char max is 20
int max is 4096
float max is 2.000000`
空结构
GCC 允许 C结构没有成员(结构的大小为零)。
|-------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| ` 1 2 3 4 5 6 7 8 9 10 11 `
| `#include struct empty { }; int main(int argc, const char *argv[]) { printf("sizeof struct empty is %ldn", sizeof(struct empty)); return 0; }`
|
`$ gcc empty.c -Wall -Werror -o empty
$ ./empty
sizeof struct empty is 0`
在 C++ 中,空结构是语言的一部分。
案例范围
在 GCC 中,我们可以在单个case标签中以"case start ... end "的格式指定一系列连续的值。很疯狂吧?
这与适当数量的单独案例标签具有相同的效果,从start 到end(含)的每个整数值对应一个标签。
|----------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 `
| `#include void ranges(char c) { switch(c) { case '0' ... '9': printf("[%c] is a number.n", c); break; case 'a' ... 'z': printf("[%c] is a lowercase letter.n", c); break; case 'A' ... 'Z': printf("[%c] is an uppercase letter.n", c); break; default: printf("[%c] is not a valid character!n", c); break; } } int main(int argc, const char *argv[]) { ranges('a'); return 0; }`
|
`$ gcc ranges.c -Wall -Werror -o ranges
$ ./ranges
[a] is a lowercase letter.`
零长度数组
GCC 允许声明零长度数组。
零长度数组可以用作结构体的最后一个元素,例如,用作可变长度对象的标头。在这种情况下,零长度数组的名称可以用作指向动态分配对象的指针。
|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 `
| `#include #include struct msg_header { int type; int size; char content[0]; }; int main(int argc, const char *argv[]) { struct msg_header *msg; int size = 1024; msg = (struct msg_header *) malloc(sizeof (struct msg_header) + size); msg->type = 0x01; msg->size = size; printf("msg header is at %pn", (void *)msg); printf("msg content is at %pn", (void *)msg->content); return 0; }`
|
`$ gcc array.c -Wall -Werror -o array
$ ./array
msg header is at 0x55c428265260
msg content is at 0x55c428265268`
还有许多其他扩展,但是我们真的应该使用它们吗?
我们应该使用 GCC 扩展吗?
所有示例均使用 GCC 7.4.0 编译。
这些扩展无需任何额外的编译器标志即可使用,因为默认情况下 GCC 7.4 使用*-std=gnu11*进行编译,这意味着支持 ISO C11 标准和 GCC 扩展。
虽然其中一些扩展确实很有用,但其代价是可移植性。使用 GCC 扩展,我们可能会在使用其他编译器构建程序时遇到问题。
例如,Clang有自己的一组扩展,并且不支持所有 GCC 扩展,如嵌套函数。
`$ clang nested.c -o nested
nested.c:5:19: error: function definition is not allowed here
void p (void) { printf("Inside a nested function!n"); }
^
nested.c:7:5: warning: implicit declaration of function 'p' is invalid in C99 [-Wimplicit-function-declaration]
p();
^
1 warning and 1 error generated.`
如果您想知道是否正在使用任何 GCC 扩展,请启用*-pedantic* 编译器选项来生成警告或启用*-pedantic-errors*编译器选项来生成错误。
`$ gcc arrayzero.c -o arrayzero -pedantic-errors
arrayzero.c:7:7: error: ISO C forbids zero-size array ‘content’ [-Wpedantic]
char content[0];
^~~~~~~`
*并且,如果您想使用 GCC 扩展并仍保持程序的可移植性,则可以使用宏__GNUC__*进行条件编译来测试这些功能的可用性,该宏始终在 GCC 下定义。
GCC文档中提供了 C 语言的 GCC 扩展的完整列表。
现在来个挑战吧!你能通过阅读源代码,找到下面程序中使用的两个 GCC 扩展吗?
|-------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|
| ` 1 2 3 4 5 6 7 8 9 10 11 12 13 `
| `#include int main(int argc, const char *argv[]) { int a = 10; int x = 0; a = x ? : 0b0010; printf("a is %d.n", a); return 0; }`
|