在了解了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}
}