C/C++/gcc不常用语法和用法合集

May 14, 2016 at 11:10 am

C、C++的语法涵盖面很广,还有一些初看比较难懂的用法,包括宏的使用、大小为0的数组等,另外gcc编译器还支持一些编译器优化相关的特殊用法,这些语法在大多数情况下不被用到,但是偶尔看到时又容易令人感到困惑,所以本篇用来做一个索引页,来包含这方面相关的内容。对于简单描述就可以理解的部分,直接放在本篇中,而对于需要更长篇幅描述的内容,以链接的形式给出。
本文包含的内容网上其实都有了,这里一方面是做一个汇总,另一方面是网上对于某个特性描述过多、过全而难以记忆,这里仅给出自己碰到的,方便自己记忆;有些特性描述过少,这里再给出自己的一些使用示例,增强理解。

[gcc]长度为0的数组

一个比较常见的形式如下所示:

struct A {
    int len;
    char data[0];
};

这不是标准的C语法(ANSI C),但是gcc编译器支持。它通常被放置在一个结构体的最后,表示的结构体有一个定长的头部和一个变长的数据(比如TCP段就有这样的性质)。
和使用char *data不同的是,data数组所包含的内存和整个结构体的内存是连续的,可以使用malloc分配一块连续的内存:

int main() {
    int len_data = 10;
    struct A* p_a;
    p_a =(struct A*)malloc(sizeof(struct A) + len_data);
    p_a->len = len_data;

    printf("%d\n", &p_a->len); // 30703632
    printf("%d\n", p_a->data); // 30703636( sizeof(int) == 4 )
    printf("%d\n", p_a->data + 1); // 30703637
    printf("%d\n", p_a->data + 5); // 30703641

    free(p_a);

    return 0;
}

下面的链接是GCC官方的说明:长度为0的数组

[C/gcc/C++11]offsetof宏

offsetof宏最初用于在编译时期计算一个结构体成员在结构体中的偏移,它的第一个参数为结构体名,第二个参数是成员名。由于C/C++保证成员的实际内存位置按照定义顺序,所以我们有如下结果:

#include 
#include  // offsetof
#include  // int32_t
struct A{
    int32_t a;
    int32_t b;
    int32_t c;
    int32_t d;
};

int main() {
    printf("%d\n", offsetof(A, a)); // 0
    printf("%d\n", offsetof(A, b)); // 4
    printf("%d\n", offsetof(A, c)); // 8
    printf("%d\n", offsetof(A, d)); // 12
    return 0;
}

我们可以使用指向成员变量的指针来实现offsetof来实现这个宏,由于这个过程在编译期完成,我们可以使用指向该结构体的空指针来实现:

#define OFFSETOF(TYPE, MEMBER) ((size_t)&(((TYPE*)0)->MEMBER))

为了应付C++中的一些情况,比如重载的&操作符,gcc定义了内置记号:

#define OFFSETOF(TYPE, MEMBER) __builtin_offsetof(TYPE, MEMBER)

需要额外说明的是offsetof一般不对POD类型以外的类型使用,否则可能有未定义的行为;C++11标准把这个推广到了标准布局(standard layout)范围。

[C/C++]内存对齐使用的round_up

round_up的定义如下:

inline size_t round_up(size_t x, size_t align) {
    return (x + align - 1) & ~(align - 1);
}

将x精确到不小于x的align的最小倍数。与操作是针对2的幂的align所作的优化,这也是通常内存对齐所要用的。
下面是针对任意对齐的版本:

inline size_t round_up(size_t x, size_t align) {
    return (x + align - 1) / align * align;
}

[gcc]always_inline属性

gcc可以使用always_inline属性强制内联一个函数,即使没有使用优化选项。如果内联失败,编译器会报错:
inline void foo (const char) __attribute__((always_inline));
注意即使使用这个属性,通常来说inline关键字还是不能少的。

[gcc]aligned属性

gcc可以使用aligned属性指定一个变量、结构体(以及函数)的最小对齐值,如下面的例子:

struct B {
    int8_t a: 1;
    int8_t b: 1;
    int8_t c: 1;
} __attribute__((aligned (32)));

struct C {
    B bb;
    int8_t c;
};

int main() {
    printf("%d\n", offsetof(C, c)); // 32
    printf("%d\n", sizeof(C)); // 64
}

虽然结构体B只包含3个位域,正常情况下只需要1个字节,但是使用了aligend属性之后,至少占用32个字节,这导致了类C中c的偏移为32(offsetof的内容参看本文offsetof部分);由于结构体整体以结构体中最大的成员对齐,所以虽然c只占8个字节,整个结构体的大小为64。

[gcc]packed属性

gcc可以使用packed属性指定一个变量按照最小对齐方式对齐,对于位域,按照位对齐,对于其它变量,按照字节对齐:

struct B {
    int8_t a: 1;
    int8_t b: 1;
    int8_t c: 1;
};

struct C {
    B bb;
    int32_t c __attribute__((packed));
};

int main() {
    printf("%d\n", OFFSETOF(C, c)); // 1
}

-----------------------------------------------------
以下内容于2017/01/18更新
-----------------------------------------------------

[gcc]format属性

gcc提供了一些可以用于函数的属性,这里我们介绍的是进行参数检查的format属性。我们知道,当我们使用printf之类的内置函数时,如果传入的格式参数存在问题,比如需要的是字符串%而实际参数是数字,如下例:

int c = 0;
printf("%s\n", c);

出现这种情况一般来说是程序编写过程中出现了纰漏。虽然上述代码在运行时可能会马上发现错误,但这个代码是可以编译通过的,不过我们注意到gcc编译器在这种情况下会给我们一些提示信息,告诉我们这里提供的参数和所需参数类型不同,下图是一个提示示例:
Selection_003
除了printf以外,还有像scanf,strftime等函数。这个功能缺省情况下仅限于内置函数,如果自己编写一个变参数的函数,默认是不会有类型检查和提示的,而format属性就是用来告诉编译器我们需要这种检查。format属性的基本表达式如下:

format (archetype, string-index, first-to-check)

其中archetype表示要检查的格式的结构,目前支持的有printf,scanf,strftime,strfmon 4种(也可以使用带前后双下划线的表示__printf__, __scanf__, __strftime__, __strfmon__,format关键字也可以使用__format__表示)。第2个参数表示我们的格式串在参数中的序号,这里的参数需要从左以1开始。第3个参数则是实际参数的起始序号,下面给出一个简单的示例:

extern int
    my_printf (void *my_object, const char *my_format, ...)
        __attribute__ ((format (printf, 2, 3)));

表示以和printf相同的方式对参数类型进行检查,格式串是第2个参数,而实际参数从第3个参数开始。