考虑以下代码:
typedef struct Object
{
uint8_t size;
uint8_t array[];
}Object;
如何使用编译时但用户定义大小的数组创建对象?
在阅读了一些stackoverflow的帖子并亲自尝试了代码之后,我现在知道,只要我至少使用编译器标准C99,我就可以用这样的自定义大小声明对象:
Object o = {.size = 2, .array = {1, 2}};
但我想要的只是一个自定义大小的空数组,所以不用键入
Object o = {.size = 5, .array = {1, 2, 3, 4, 5}};
我想键入类似的内容
Object o = {.size = 5, .array = uint8_t[5]};
同样重要的是,我需要在编译时知道数组大小,并且不能使用像malloc这样的动态内存,所以只使用malloc分配的灵活数组成员的典型解决方案对我来说不起作用
这样的解决方案存在吗?
如果不存在:为什么第一个例子(对于某些编译器)有效,但仅仅告诉编译器我在编译时需要结构中的一个大小为10的数组而不初始化其中的值就不行了?
编辑:我想我应该明确表示,我的目标是创建类似于C++std::array
的东西,因为对于该数据类型,用户只需选择数组的大小,而不必知道内部工作原理,也不必为每个新的大小重写结构。
您可以使用在复合文字中定义的并集:
Object *o = &(union { Object o;
// anonymous struct
struct { uint8_t size; uint8_t array[N]; };
} ) { .size = N }.o;
它可以很好地封装到宏中。
它在迂腐模式下编译时没有任何警告,为全局对象和堆栈分配的对象都产生了预期的结果。看见https://godbolt.org/z/jhsbMseY8
这个解决方案是有保证的,因为在C中,联合的两个结构成员可以别名,只要它们共享公共的初始子序列。
我建议您为所需的每种大小的Object
定义一种类型。
你可以通过定义一些宏来简化它:
#include <stdint.h>
#include <stdlib.h>
#define OBJINIT(x) (Object##x){.size = x }
#define DEFOBJ(x)
typedef struct
{
uint8_t size;
uint8_t array[x];
} Object##x;
Object##x *CreateObject##x() {
Object##x *rv = malloc(sizeof *rv);
if(!rv) exit(1);
*rv = OBJINIT(x);
return rv;
}
// Define the types with the sizes you need:
DEFOBJ(2)
DEFOBJ(5)
int main(void) {
Object2 o2 = OBJINIT(2); // an initialized automatic variable
Object5* o5p = CreateObject5(); // an initialized dynamically allocated variable
free(o5p);
}
像这样分配数组大小的正常方法是使用宏,这样您就可以轻松地更改它并在代码的其他地方使用它的大小。
#define MYARRAYSIZE 10
typedef struct
{
uint8_t size;
uint8_t array[MYARRAYSIZE];
}Object_t;
另一种方法是使其成为指针,然后在运行时使用malloc动态分配内存。可以通过这种方式声明更大的数组。
#define MYARRAYSIZE 10
typedef struct
{
uint8_t size;
uint8_t *array;
}Object_t;
...
Object_t Object;
Object.array = (uint8_t *)malloc(MYARRAYSIZE*sizeof(uint8_t));
如果使用方言,通过放松";严格混叠规则";足以维护C原则的精神";不要阻止程序员做需要做的事情";,一种常见的方法是这样做:
struct polygon { int sides; int coords[]; };
struct poly5 { int sides; int coords[10]; }
const myPentagonn = {5, {0,-5, 4,-3, 3,5, -3,5, -4,-3} };
#define myPentagon (*((struct polygon*)&myPentagonn))
在标准前的方言中,通常需要将多边形声明为包含零元素数组,或者如果编译器认为这很烦人,则需要声明为包含单个元素数组,但毫无疑问,函数是否应该能够像这里所示的那样可互换地作用于多个类似的结构类型。经过一些调整,这种结构在1974年的C方言中得到了认可,适合低级编程的方言将支持它们,而不考虑标准是否会强制要求这种支持。
一些clang/gcc粉丝不接受已发布的基本原理文件中所表达的标准作者的意图,他们会坚持认为,任何需要使用-fno严格混叠的代码都是错误的。然而,本标准的目的是将对此类结构的支持视为本标准管辖范围之外的实施质量问题。旨在用于从不需要或受益于任何类型的低级存储器操作的任务的实现对于这样的任务可能比适应这样的需求的实现更有效,但这并不意味着打算在低级别使用内存的代码应该跳过重重关卡,与这种专门的实现或配置兼容。
#include <stdio.h>
#include <stdint.h>
typedef struct Object
{
uint8_t size;
uint8_t array[5];
} Object;
int main() {
Object o = { .size = 2, {1, 2}};
printf("%d %dn", o.array[0], o.array[1]);
}
这是在macOS上用clang 14.0.0中的cc -std=c99
为我编译的。它输出:
1 2
首先,每当你发现自己有这样的奇异需求时,请退一步,看看你是否不能简化程序。使用晦涩难懂的宏技巧总是最后的手段——即使这样,在维护旧代码时也是如此。
话虽如此,最不晦涩的解决方案可能是根据变量名为每个对象创建一个唯一的typedef。然后在同一宏中声明该类型的变量。这样就可以在任何地方使用它。
一种可能的解决方案:
#define create_obj(name, ...)
typedef struct
{
uint8_t size;
uint8_t array[sizeof( (uint8_t[]){__VA_ARGS__} )];
} obj_##name;
obj_##name name =
{
.size = sizeof( (uint8_t[]){__VA_ARGS__} ),
.array = { __VA_ARGS__ }
}
将创建一个附加了变量名的类型。它将不使用灵活的数组成员,而是使用固定大小的数组。它不需要动态分配,也不需要依赖于奇异的角落案例规则或非标准扩展。
如果要使用uint8_t
以外的其他成员类型,那么只需使用常用技巧确定数组大小:
sizeof((type[]){ __VA_ARGS__ }) / sizeof(type[0])
示例:
#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>
#define create_obj(name, ...)
typedef struct
{
uint8_t size;
uint8_t array[sizeof( (uint8_t[]){__VA_ARGS__} )];
} obj_##name;
obj_##name name =
{
.size = sizeof( (uint8_t[]){__VA_ARGS__} ),
.array = { __VA_ARGS__ }
}
create_obj(filescope_obj,1,2,3);
int main (void)
{
create_obj(local_obj,1,2,3,4,5);
for(size_t i=0; i<filescope_obj.size; i++)
{
printf("%"PRIu8 " ", filescope_obj.array[i]);
}
puts("");
for(size_t i=0; i<local_obj.size; i++)
{
printf("%"PRIu8 " ", local_obj.array[i]);
}
}
输出:
1 2 3
1 2 3 4 5