GNU C对ISO标准的扩展——笔记(二)

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 noinlinealways_inline, pure, const, format, format_argno_instrument_functionsection,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> 字符。

你可能感兴趣的:(c,扩展,编译器,attributes,destructor,structure)