关闭

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

2677人阅读 评论(0) 收藏 举报

在朋友的博客上看到这样的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))等价,它的意思就是如果a0,则给b赋值为5,否则,给c赋值为5.
对于类型转换,这种规则不知道在什么地方会带来好处。

Conditionals with Omitted Operands

就是在?:表达式中,可以省略项。比如:x?x:y 可以写成 x?:y

Double-Word Integers

支持64位的整数,这是GCCC89中的扩展,在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/16p3表示乘上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的数组可以出现在任意位置。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:834092次
    • 积分:10059
    • 等级:
    • 排名:第1759名
    • 原创:182篇
    • 转载:25篇
    • 译文:0篇
    • 评论:201条
    最新评论
    JLU GridLab