在朋友的博客上看到这样的
C
语言初始化方式:
int
arr[10] = { [5] = 7 }; // a[5] = 7;
以前没有用过也不知道有这样的写法。在网上找了一下这方面的资料,最后在
http://gcc.gnu.org/onlinedocs/gcc-3.2.3/gcc/C-Extensions.html
找到了答案。以下所有内容都来自这个网址。
上面的那行
C
语言初始化语法叫做
Designated Initializers
,是
GNU C
编译器对
ISO C
标准的扩展。在
gcc
编译器中使用
-pedantic
选项,就可以让
gcc
找出代码中所有
GNU C
的扩展语法并输出警告信息。如果要使用条件编译来检测这些扩展特性是否可用,只需要检测宏
__GNUC__
是否被定义。
这些扩展特性中的一部分特性已经被
C99
标准承认。以下是对上述网址处的内容的笔记:
Statements and Declarations in Expressions
使用圆括号括起来的复合语句
(
复合语句是用一对花括号括起来来的语句,所以这种语法的形式就是一对花括号外面再括上一对圆括号,具体例子见下面
)
地位上相当于一个普通表达式
,可以在其中使用循环、条件分支以及局部变量。下面是一个例子:
({
int
y = foo ();
int
z;
if
(y > 0) z = y;
else
z = - y;
z; })
这段代码展示了这种语法的形式。复合语句中的最后一条语句
(
或者表达式
)
的值代表了整个这个语法结构的值
(
和逗号表达式一样
)
,在上面这段代码来说,它的值就是最后
z
的值。
这种技术在宏定义中特别有用,它引入了局部变量,消去了宏定义中变量可能被多次求值的问题。例如,可以这样定义求两个整数的最大值的宏:
#define
maxint(a,b) /
({
int
_a = (a), _b = (b); _a > _b ? _a : _b; })
Locally Declared Labels
每一条表达式语句
(statement expression)
都定义了一个作用域,在这个作用域内可以声明一个或多个局部标号。声明标号的语法形式为:
__label__ label;
或者声明多个标号:
__label__ label1, label2, ...;
局部标号的声明必须出现在表达式语句的最开始的地方,也就是紧接着
({
的地方。这种局部标号的技术为上面的表达式语句
(statement expression)
技术带来了很大的方便。因为通常表达式语句出现在宏定义中,宏定义可能会有嵌套的循环,在这种情况下局部标号可以方便地跳出循环体。因为它的生存期仅在包含它的表达式语句中,所以含有局部标号的宏在同一个函数中多次展开不会引起标号的重复定义。下面是这种技术的应用体现,其中还有
typeof
语法,也属于扩展特性,后面会有介绍:
#define
SEARCH(array, target) /
({ /
__label__ found; /
typeof
(target) _SEARCH_target = (target); /
typeof
(*(array)) *_SEARCH_array = (array); /
int
i, j; /
int
value; /
for
(i = 0; i < max; i++) /
for
(j = 0; j < max; j++) /
if
(_SEARCH_array[i][j] == _SEARCH_target) /
{ value = i;
goto
found; } /
value = -1; /
found: /
value; /
})
Labels as Values
可以对定义在当前作用域内的标号使用操作符
&&
取地址,取得的地址的类型是
void*
。下面是这种语法的演示:
void
labelDemo() {
__label__ first, second, third, leave;
int
cnt = 0;
void
info(
const
char
* msg) {
puts
(msg);
cnt++;
printf
(
"cnt = %d/n"
, cnt);
}
void
*pFirst, *pSecond, *pThird;
void
*arr[] = { &&first, &&second, &&third };
const
int
array[] = {0, &&second - &&first, &&third - &&first };
first:
info(
"first"
);
second:
info(
"second"
);
if
(cnt == 10)
goto
leave;
third:
info(
"third"
);
pFirst = &&first;
pSecond = &&second;
pThird = &&third;
goto
*pSecond;
goto
*arr[0];
goto
*(&&first + array[2]);
leave:
puts
(
"leave"
);
}
Nested Functions
嵌套函数是又一个特性。它有以下特点:
1.
其函数名只在它的定义所在的作用域内有效。
2.
嵌套函数可以访问外部函数中定义在该嵌套函数之前的所有变量,包括外部函数中的
local labels
,可以直接从嵌套函数中
goto
到外部函数的
labels
。
3.
嵌套函数的定义必须出现在所有语句之前,或者它的前向声明出现在所有语句之前。这和
C
语言中函数局部变量声明规则一样。
4.
可以把嵌套函数的地址保存起来以供调用。特别地,可以把嵌套函数的地址作为参数传给其它函数调用。
5.
嵌套函数永远是内部连接的。如果想在一个嵌套函数定义前就调用它。需要使用显式的
auto
关键字提前声明。
下面是一个展示嵌套函数的例子:
void
f(
void
(*g)(
const
char
*)) {
(*g)(
"this is in f function"
);
}
void
test() {
__label__ failure;
int
a = 4;
auto
void
accessOuter();
auto
void
print(
const
char
* msg);
auto
void
bad() {
printf
(
"bad function, goto failure/n"
);
goto
failure;
}
printf
(
"test/n"
);
accessOuter();
f(print);
bad();
printf
(
"can not come here/n"
);
void
accessOuter() {
printf
(
"%d/n"
, a);
}
void
print(
const
char
* msg) {
printf
(
"%s/n"
, msg);
}
failure:
printf
(
"failure/n"
);
}
Constructing Function Calls
主要是提供了三个编译器内建函数:
__builtin_apply_args, __builtin_apply, __builtin_return
。三个函数的原型为:
void * __builtin_apply_args ();
void * __builtin_apply (void (*function)(), void *arguments, size_t size);
void __builtin_return (void *result);
假如函数
f
中需要调用另一个函数
g
,并且传给
g
的参数和传给
f
的参数完全一样,则在
f
中可以使用第一个函数
__builtin_apply_args
构造参数,使用
__builtin_apply
完成
g
的调用,使用
__builtin_return
保存
g
的返回值。这些好像在
PHP
中有等价的概念。网上没有给出例子说明,但自己觉得这种技术在可变参数函数调用中特别有用,简化了非常多的工作,举例如下:
void
error(
const
char
* fmt, ...) {
void
*args = __builtin_apply_args();
fprintf
(stderr,
"error: "
);
fflush
(stderr);
__builtin_apply(
printf
, args, 100);
}
int
main() {
int
i = 3;
error(
"this is just a test. file: %s, line: %u, i = %d/n"
,
__FILE__, __LINE__, i);
return
0;
}
如果没有这种技术,而要在
error
函数中调用
printf
函数,就非常困难了,除非使用
va_start,va_end
这些宏构造参数,然后调用
vfprintf
,然而有了这三个函数,就非常简单。
Referring to a Type with typeof
这是一个非常神奇非常有用的技术。跟
C++
中的
RTTI
差不多。网上给出了非常精彩的应用。可以给
typeof
传递两种参数:表达式或者类型。
typeof
的语法和
sizeof
相似,但非常精彩的是,
typeof
在语义上更像是用
typedef
定义的一个类型名。比如,以下代码会输出
3
:
int
a;
typeof
(a) b = 3;
printf
(
"%d/n"
, b);
以下是更精彩的使用,在宏定义中使用
typeof
:
#define
max(a,b) /
({
typeof
(a) _a = (a); /
typeof
(b) _b = (b); /
_a > _b ? _a : _b; })
还有精彩的例子,使用它们写出非常自然的代码:
#define
Pointer(T)
typeof
(T *)
#define
Array(T, N)
typeof
(T [N])
Array(Pointer(
char
), 4) y;
它就等价于定义
char *y[4]
。
Generalized Lvalues
这一条扩展使得对于复合表达式
(
比如逗号表达式
)
、条件表达式
(?:
表达式
)
和类型转换表达式,只要它们的操作数是左值,它们就也可以作为左值。
比如:
(a, b) += 5;
与
a, (b += 5);
等价。整个逗号表达式的值是
b+5
,之后
b
的值多了
5.
(a ? b : c) = 5
与
(a ? b = 5 : (c = 5))
等价,它的意思就是如果
a
非
0
,则给
b
赋值为
5
,否则,给
c
赋值为
5.
对于类型转换,这种规则不知道在什么地方会带来好处。
Conditionals with Omitted Operands
就是在
?:
表达式中,可以省略项。比如:
x?x:y
可以写成
x?:y
。
Double-Word Integers
支持
64
位的整数,这是
GCC
在
C89
中的扩展,在
ISO C99
被正式支持。使用
long long int
来声明
64
位有符号整数的变量,使用
unsigned long long int
声明
64
位无符号整数变量。要标明一个整数是
64
位有符号整数,在这个整数末尾加上
LL
后缀,标明它是一个无符号
64
位整数,在它末尾加上
ULL
后缀。
64
位整数的加减运算和异或运算在所有机器上都支持,乘法运算、除法运算和移位运算需要特殊的硬件支持。
Complex Numbers
复数支持。
Hex Floats
浮点数的十六进制输出。比如
1.55e1
表示成十六进制就是
0x1.fp3
,其中
0x
是十六进制前缀,
1
是有效数字整数
1
,小数点后的
f
的权值是
16^-1
,也就是表示十进制的
15/16
,
p3
表示乘上
2^3
,所以
0x1.fp3 = (1 + 15 / 16) * 8
。
Arrays of Length Zero
它所要解决的问题在《
C Programming FAQs
》问题
2.7
有详细的讲解,这里的很多内容也都是《
C Programming FAQs
》上讲过的。比如下面这个结构体
(
例子代码来自《
C Programming FAQs
》
)
:
struct
name {
int
namelen
;
char
namestr
[0]
};
其中的
namestr
成员,程序员不能提前知道应该给它分配多大空间。在
ISO C89
下,程序员解决这个问题的方法是声明
char namestr[1]
,然后运行时动态
malloc
。这里长度为
0
的数组主要是为了让
malloc
时不用刻意少分配一个字节
(
如果是长度为
1
则需要考虑长度换算
)
。
C99
中引入了灵活数组的概念,是为了更方便地解决这个问题。灵活数组在语法和语义上与长度为
0
的数组有些细微的差别,表现在如下方面:
1.
灵活数组是写成
[]
的形式而不是长度为
0.
比如
char name[]
而不是
char name[0]
。
2.
灵活数组是类型不完全的变量,因此不能使用
sizeof
求其字节数,而长度为
0
的数组可以用
sizeof
计算,结果是
0.
3.
灵活数组只能是结构体中最后一个成员,但长度为
0
的数组可以出现在任意位置。