Arrays of Variable Length
变长数组,
ISO C99
标准中正式支持,在
C89
模式下作为
GCC
的扩展特性。变长数组即数组长度为变量,除此之外与普通数组在声明语法上没有区别。变长数组在声明处分配空间
(
在栈上分配空间而不是在堆上,这一点与函数
alloca/_alloca
相同,实际上用
gdb
调试发现,变长数组的空间分配就是通过调用
alloca
完成的
)
,在作用域结束时自动收回空间
(
包括因为使用
goto
或者其它手段中途结束
)
。
变长数组与使用
alloca/_alloca
分配空间的数组的区别在于:
alloca
分配的空间在调用
alloca
的函数返回时收回,而变长数组的空间在变长数组名字作用域结束时收回
(
通常是包含它的大括号结束
)
。如果在一个函数中既使用
alloca
又使用变长数组,那么变长数组空间收回会导致最近一次的
alloca
空间被收回。
也可以把变长数组用作函数的参数。下面是一段变长数组的演示代码:
int
n, t;
scanf
(
"%d"
, &t);
while(
t--) {
scanf
(
"%d"
, &n);
({
int
arr[n], i, sum = 0;
for(
i
= 0; i < n; i++) {
scanf
(
"%d"
, arr+i);
sum += arr[i];
}
printf
(
"sum is: %d/n"
, sum);
});
}
调试时遇到了一段不理解的代码,暂时附在这里:
0x00401628 <varlen_array+26>: mov 0x8(%ebp),%eax
0x0040162b <varlen_array+29>: dec %eax
0x0040162c <varlen_array+30>: inc %eax
0x0040162d <varlen_array+31>: add $0xf,%eax
0x00401630 <varlen_array+34>: shr $0x4,%eax
0x00401633 <varlen_array+37>: shl $0x4,%eax
先减
1
再加
1
,然后加上
15
,右移
4
位再左移
4
位,不知道为什么要这么做。而且在调试中发现,代码中调用
alloca
,编译器直接用
sub
指令移动
esp
指针,如果使用变长数组,反而去调用
alloca
了。
Macros with a Variable Number of Arguments.
宏可以
像函数一样接受可变参数列表,
C99
正式支持这样的语法。这种宏的声明语法也和可变参数函数的声明语法一样。例如:
#define
debug(format, ...) fprintf (stderr, format, __VA_ARGS__)
首先复习一下在没有这种技术的情况下,如何实现可变参数宏的效果:
方法一:
#define
debug(args) fprintf(stderr, args)
但这样的宏不行。把它改成这样
(
就是说
debug
宏不能
扩展成
fprintf
的形式
)
:
方法二:
#define
debug(args) printf args
这样实际上
debug
宏仍然
还是一个参数,所以调用时必须要加上一对额外的括号,例如:
debug(
(“i = %d/n”, i));
方法三:
根据参数数量使用不同的宏。很常见的就是在
CRT DEBUG
库中,有
_RPTn
,
RPTFn
这些宏
,
n
是参数个数,比如
RPTF0
表示宏只带
一个参数,
RPTF1
表示带两个参数。在
MFC
中,也有
TRACEn
这样的根据参数个数分别定义的调试宏。
方法四:
在调试宏中使用如
”
i
= %d/n”, i
这样的字符串,中间的逗号分隔了参数,这也是方法二要加上一对额外括号的原因。所以,如果能通过适当的手段,让编译器在宏展开时暂时不知道这里有个逗号,就可以解决问题。如下:
#define
DEBUG(args) printf(args)
#define
_ ,
调用时:
DEBUG(
"i = %d/n"
_ i);
记住调用
DEBUG
宏时
_
左右要有空格。
方法五:
#define
DEBUG printf
调用时:
DEBUG(
"i = %d/n"
, i);
这是最直接最简单的方法。完全可以自己写一个
printf
这样的函数
(
用
stdarg.h
中的支持或者使用前面说的三个
builtin
函数
)
。
上面的五种方法多数参考自《
C Programming FAQs
》问题
10.26.
Slightly Looser Rules for Escaped Newlines
多行宏定义中要使用反斜杠表示续行,反斜杠后面必须紧跟换行符,在反斜杠和换行符之间不能有任何空白字符。这一条扩展使得在反斜杠和换行符之间可以有空格
(ASCII
码
32)
、水平制表符
(ASCII 9)
、垂直制表符
(ASCII 11)
、走纸符
(ASCII 12)
。不能有注释。
String Literals with Embedded Newlines
允许字符串字面值跨越多行而不需要对内嵌的换行符进行转义。
Non-Lvalue Arrays May Have Subscripts
ISO C99
标准正式支持。任何非左值的数组仍然被作为退化的指针,可以对它们使用取下标运算符,尽管它们不能被修改、不能在下一个序列点之后使用、不能对它们使用取地址运算符。例如下面代码:
struct
foo {
int
a
[4];};
struct
foo f();
bar
(
int
index)
{
return
f().a[index];
}
bar
函数体中的
return
语句体现了这种用法。
关于什么是序列点
(sequence point)
,在《
C Programming FAQs
》问题
3.9
中有详细解释,摘抄如下:
C
语言标准中提及的序列点包括:
完整表达式
(full expression
,表达式语句或者不是任何其他表达式的子表达式的表达式尾部
)
;
||, &&, ?:
或逗号表达式的操作符处;
函数调用时
(
参数传递完毕,函数被实际调用之前
)
。
Arithmetic on void
- and Function-Pointers
GNU C
的这一项扩展使得能够对
void
指针和函数指针进行加减运算,以及
sizeof
运算。
void
指针和函数指针大小被看作
1
,相当于字符指针。
在
GNU C
编译器中有
-Wpointer-arith
选项。这个选项使编译器对源代码中的这种指针算术发出警告。
Non-Constant Initializers
在对
auto
数组变量进行
aggregate initialize(
就是像
int
a[100] = {1, 2, 3};
这样的初始化语句
)
时,这里的初始化值不必要是常量表达式。举例如下:
foo
(
float
f,
float
g)
{
float
beat_freqs[2] = { f-g, f+g };
...
}
C++
标准和
ISO C99
标准都正式支持这一特性。
Compound Literals
ISO C99
标准正式支持这种语法。按字面翻译叫做“复合字面值”,这种语法看起来像是一个强制类型转换,实际上就是对一个初始化的强制类型转换。它的语法如下:
(type-name) {initializer-list}
这种语法创建一个无名的对象,它使得初始化一个对象不再需要先初始化一个临时变量,为对象的初始化提供了一种轻便的方法。它是一个左值
(
它在栈上分配了空间,只是没有名字
)
。例如:
struct
foo {
int
a
;
char
b
[2];} structure;
structure
= ((
struct
foo) {x + y,
'a'
, 0});
上面对
structure
的初始化等价于下面的语句:
{
struct
foo temp = {x + y,
'a'
, 0};
structure = temp;
}
再如:
struct
foo *s1 = &(
struct
foo){1, 2,
"3"
};
printf
(
"%d/n"
,
sizeof
(*s1));
输出
12.
下面是另外几个例子:
struct
foo x = (
struct
foo) {1,
'a'
,
'b'
};
int
y[] = (
int
[]) {1, 2, 3};
int
z[] = (
int
[3]) {1};
它们分别等价于下面的初始化语句:
struct
foo x = {1,
'a'
,
'b'
};
int
y[] = {1, 2, 3};
int
z[] = {1, 0, 0};
一旦它所在的作用域结束,栈上分配的空间也被收回。
(
注:上面都是转换成
struct
和数组,对于
union
,有所不同,后面有专门针对
union
的
)
。
Designated Initializers
ISO C99
标准正式支持。可以使用任意的顺序来初始化数组元素或者结构体的成员,而不必按照元素被声明的顺序。它有以下三种形式的写法:
初始化数组中的某个元素,语法为:
[index] = value
例如:
int
a[6] = { [4] = 29, [2] = 15 };
它等价于:
int
a[6] = { 0, 0, 15, 0, 29, 0 };
初始化数组中的一个区间,语法为:
[first … last] = value
例如:
int
widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
这是一项
GNU
的扩展。
初始化结构体中的成员,语法为:
.fieldname = value
例如:
struct
point p = { .
y
= yvalue, .
x
= xvalue };
上面的
[index]
和
.fieldname
被称为
designator
,这种初始化语法叫做
designated initializer
。
下面是一些其它例子:
int
a[6] = { [1] = v1, v2, [4] = v4 };//a[1]=v1,a[2]=v2,a[4]=v4,
其它为
0
int
whitespace[256]
= { [
' '
] = 1, [
'/t'
] = 1, [
'/h'
] = 1,
[
'/f'
] = 1, [
'/n'
] = 1, [
'/r'
] = 1 };
struct
point ptarray[10] = { [2].
y
= yv2, [2].
x
= xv2, [0].
x
= xv0 };
struct
st{
int
a
[5],
b
;};
struct
st *w=(
struct
st []){[3]={1,2},[1]={.
a
={[3]=1},.
b
=1} };
Cast to a Union Type
强制类型转换为一个
union
,但产生的不是一个左值。
Case Ranges
可以在
case
行标中
指定一个区间,使用下面的语法:
case
low ... high:
注意
low, …, high
之间都有空格,否则会导致语法错误。另外,
case
行标必须
是常量,如:
int
a[] = {1, 2, 3, 4, 5, 6}, b;
scanf
(
"%d"
, &b);
switch(
b) {
//case a[0] ... a[2]: // wrong
case
1 ... 3:
puts(
"first block"
);
break
;
//case a[3] ... a[5]: // wrong
case
4 ... 6:
puts(
"second block"
);
break
;
}
Mixed Declarations and Code
像
C++
一样允许变量随处声明。在
C99
标准中被支持。
Declaring Attributes of Functions
使用
__attribute__
关键字声明函数的属性。和
visual studio
中的
__declspec
相当。语法形式如下:
__attribute__ ((attribute-list))
下列属性是
GNU C
目前为所有目标机器上定义的通用属性:
noreturn
, noinline, always_inline, pure, const, format, format_arg, no_instrument_function, section,constructor, destructor, used, unused, deprecated, weak, malloc, alias
在
http://gcc.gnu.org/onlinedocs/gcc-3.2.3/gcc/Function-Attributes.html#Function%20Attributes
有
GNU C
定义的完整属性列表和详细解说。该网址上同时给出了为什么要定义
__attribute__
关键字而不是
#pragma
指令来做这些事情的两条原因:一是不可能从一个宏定义中产生
#pragma
指令,我觉得最明显的体现就是编译器通常将
dllexport
和
dllimport
属性定义为宏。二是不同的编译器有可能对同一条
#pragma
指令做出不同的解释。
Prototypes and Old-Style Function Definitions
主要是冲着新的函数声明和旧的函数声明来的。自从接触代码的时候就只在很旧的程序设计书中看到过旧的形式的函数声明。这一条没有什么意义。在
C++
中禁止出现旧的函数声明形式。
C++ Style Comments
允许在
C
代码中使用以
//
开头的行注释。
Dollar Signs in Identifier Names
GNU C
编译器允许标识符中使用美元符。一些目标机器上不允许,因为和汇编冲突。
The Character <ESC> in Constants
使用
/e
转义表示
<ESC>
字符。