GCC复合文字在C (GCC C11)宏中



在了解了GCC支持复合字量之后,可以使用{…} initaliser。

然后考虑如果最后一个元素是可变长度的item, gcc接受(有限制)可变长度的结构。

我希望能够使用宏来填写许多表,其中大多数数据从编译时保持不变,只有少数字段改变。

我的结构很复杂,所以这里有一个更简单的工作示例作为开始,以演示如何使用它。

#include <stdio.h>
typedef unsigned short int uint16_t;
typedef unsigned long size_t;
#define CONSTANT -20

// The data we are storing, we don't need to fill all fields every time
typedef struct dt {
uint16_t    a;
const int   b;
} data_t;

// An incomplete structure definiton that matches the general shape
typedef struct ct {
size_t      size;
data_t      data;
char        name[];
} complex_t;
// A typedef to make the code look cleaner
typedef complex_t * complex_t_ptr; 
// A macro to generate instances of objects
#define CREATE(X, Y)  (complex_t_ptr)&((struct { 
size_t size;            
data_t data;            
char name[sizeof(X)];   
} ) {                       
.size = sizeof(X),          
.data = { .a = Y, .b = CONSTANT }, 
.name = X                   
})
// Create an array number of structure instance and put pointers those objects into an array
// Note each object may be a different size.
complex_t_ptr data_table[] = {
CREATE("DATA1", 1),
CREATE("DATA2_LONGER", 2),
CREATE("D3S", 3),
};
static size_t DATA_TABLE_LEN = sizeof(data_table) / sizeof(typeof(0[data_table]));

int main(int argc, char **argv)
{
for(uint16_t idx=0; idx<DATA_TABLE_LEN; idx++)
{
complex_t_ptr p = data_table[idx];
printf("%15s = (%3u, %3d) and is %3lu longn", p->name, p->data.a, p->data.b, p->size);
}
return 0;
}
$ gcc test_macro.c -o test_macro
$ ./test_macro
DATA1 = (  1, -20) and is   6 long
DATA2_LONGER = (  2, -20) and is  13 long
D3S = (  3, -20) and is   4 long

到目前为止一切顺利…

现在,如果我们想创建一个更复杂的对象呢?


//... skipping the rest as hopefully you have the idea by now
// A more complicated data structure
typedef struct dt2 {
struct {
unsigned char class[10];
unsigned long start_address;
} xtra;
uint16_t    a;
const int   b;
} data2_t;
// A macro to generate instances of objects
#define CREATE2(X, Y, XTRA)  (complex2_t_ptr)&((struct { 
size_t size;            
data2_t data;            
char name[sizeof(X)];   
} ) {                       
.size = sizeof(X),          
.data = { .xtra = XTRA, .a = Y, .b = CONSTANT }, 
.name = X                   
})
// Again create the table
complex2_t_ptr bigger_data_table[] = {
CREATE2("DATA1", 1, {"IO_TBL", 0x123456L}),
CREATE2("DATA2_LONGER", 2, {"BASE_TBL", 0xABC123L}),
CREATE2("D3S", 3, {"MAIN_TBL", 0x555666L << 2}),
};
//... 

但是有一个问题。这不会编译,因为编译器(预处理器)会被结构成员之间的逗号混淆。传递的结构成员中的逗号被宏看到,它认为有额外的参数。

GCC说你可以在你想保留逗号的地方用括号括起来,就像这样

MACRO((keep, the, commas))

。在本例中,就是

CREATE_EXTRA("DATA1", 1, ({"IO_TBL", 0x123456L}) )

但是对于我们得到的

结构体来说,这是行不通的
.xtra = ({"IO_TBL", 0x123456L})

这不是一个有效的初始化器。

另一个选项是

CREATE_EXTRA("DATA1", 1, {("IO_TBL", 0x123456L)} )

结果是

.xtra = {("IO_TBL", 0x123456L)}

也是无效的

如果我们把大括号放在宏

里面
.xtra = {EXTRA}
...
CREATE_EXTRA("DATA1", 1, ("IO_TBL", 0x123456L) )

我们得到相同的

显然,有些人可能会说"一次只传递一个XTRA元素"。请记住,这是一个简单的,非常精简的例子,在实践中,这样做会丢失信息,使代码更难以理解,如果只是手写复制结构,则更难维护,但更容易阅读。

所以问题是,如何将复合文字结构作为初始化器传递给宏,而不会被字段之间的逗号绊倒.

注意我被困在GCC4.8上的C11。

所以有一种方法,尽管我在GCC的宏页面上找不到它。
我在这篇文章中找到了我需要的东西:省略逗号和删除逗号

下面的作品


typedef struct _array_data {
size_t size;
char  * data;
}array_data_t;
#define ARRAY_DATA(ARRAY...) (char *) 
&(array_data_t) {                    
sizeof((char []){ARRAY}),         
(char []){ARRAY}                  
}
char * my_array =  ARRAY_DATA(1,2,3,4);
size_t sent = send_packet(my_array);
if (len != my_array->size) ERROR("Not all data sent");

这里有一些有趣的方面。

1:与gcc手册中的示例不同,{ARRAY}周围的括号被省略了。本文档中以"(cast)({structure})"为例,不使用"(cast){structure}"。事实上,看起来从来都不需要括号,只是在某些情况下(比如当你接受地址时)混淆编译器。

2:使用cast(char [])而不是(char *),因为人们会认为是正确的。

3:当然这是有意义的,但是你必须在sizeof部分也加上一个强制转换,否则它怎么知道单个字面值的大小。

为了完整起见,上面例子中的宏展开为:

char * my_array =  (char *)&(array_data_t) {                    
sizeof((char []){1,2,3,4}),
(char []){1,2,3,4};
}

任何my_array都是指向如下结构体的指针:

* my_array = {
size_t size = 4,
char data[4] = {1,2,3,4}
}

最新更新