通过过度分配内存在结构中内联可变长度数组是不是有效?

Posted

技术标签:

【中文标题】通过过度分配内存在结构中内联可变长度数组是不是有效?【英文标题】:Is it valid to inline variable length arrays in structs by over-allocating memory?通过过度分配内存在结构中内联可变长度数组是否有效? 【发布时间】:2019-07-05 01:46:59 【问题描述】:

我有一个结构,我想保留在连续的内存中,这样我就可以memcpy 整个结构等。但是我的结构包含一个可变长度的数组。 现在这个长度将在程序执行期间固定,但在编译时它是未知的。我可以通过在结构之后过度分配内存来解决这个问题吗?阵列的空间?

所以如果我从

struct license_plate
    char issuing_province_territory_code [2];
    char* number;

我需要一个单独的malloc 用于number,所以我想到了以下操作

struct license_plate_v2 
    char issuing_province_territory_code [3];
    char number[1];

并按原样分配

size_t sizeof_license_plate_v2( int number_length )
    return sizeof(struct license_plate_v2) + number_length * sizeof(char);



struct license_plate_v2* malloc_license_plate_v2( int number_length )
    return malloc( sizeof_license_plate_v2( number_length ) );

然后能够像这样遍历数组

struct license_plate_v2* index_license_plate_v2( struct license_plate_v2 *arr, int index, int plate_num_len )
    return  arr + index * sizeof_license_plate_v2(plate_num_len);


void print_all( struct license_plate_v2* plates, int num_of_plates, int plate_num_len )
    for( int plate_index = 0; plate_index < num_of_plates; plate_index++ )
        struct license_plate_v2* plate = index_license_plate_v2( plates, plate_index, plate_num_len );
        printf( "where: %s, plate: %s\n", plate->issuing_province_territory_code, plate->number  );
    

这是有效的 C 吗?这是保证工作还是我使用未定义的行为?如果数组是结构体,字节对齐有什么问题吗?有这个术语吗?这是实现这种效果的正确方法吗?

似乎可行:

#include <stdlib.h>

int main( int argc, char** argv ) 
    //these values could have from from argv for example
    int num_len = 7;


    struct license_plate_v2 *arr = malloc( 4  * sizeof_license_plate_v2(num_len) );

    struct license_plate_v2 *arr_0 = arr + 0 * sizeof_license_plate_v2(num_len);
    memcpy( arr_0->issuing_province_territory_code, "ON"      , 3           * sizeof(char) );
    memcpy( arr_0->number                         , "BFKK281" , (num_len+1) * sizeof(char) );

    struct license_plate_v2 *arr_1 = arr + 1 * sizeof_license_plate_v2(num_len);
    memcpy( arr_1->issuing_province_territory_code, "ON"      , 3           * sizeof(char) );
    memcpy( arr_1->number                         , "BYTR741" , (num_len+1) * sizeof(char) );

    struct license_plate_v2 *arr_2 = arr + 2 * sizeof_license_plate_v2(num_len);
    memcpy( arr_2->issuing_province_territory_code, "ON"      , 3           * sizeof(char) );
    memcpy( arr_2->number                         , "CAAA224" , (num_len+1) * sizeof(char) );

    struct license_plate_v2 *arr_3 = arr + 3 * sizeof_license_plate_v2(num_len);
    memcpy( arr_3->issuing_province_territory_code, "ON"      , 3           * sizeof(char) );
    memcpy( arr_3->number                         , "CASD431" , (num_len+1) * sizeof(char) );

    print_all( arr, 4, 7 );

    free( arr );   


PS-这是一个简单的例子来说明这个问题,现实世界的问题涉及到数百万个位置,具有数千个(运行但不是编译时间常数)数据点,每个数据点都是一个结构而不是 @987654329 @,所以一些明显的变通方法不适用。

【问题讨论】:

在上面的示例代码中num_len不应该是7吗? 是的。我现在已经修好了。 对齐会是个问题。即使您在struct 中显示的两个成员都是char 的数组,并且(根据C 标准)要求char 具有最弱的对齐,但结构本身可能具有更高的对齐要求。 C 要求所有指向结构的指针具有相同的表示形式,而 C 实现可能会选择通过要求所有结构具有某种最小对齐来部分满足这一点。 这被称为“结构黑客”并且没有明确定义。您正在寻找灵活的数组成员 @Lundin: struct hack 被明确定义并得到绝大多数实现的支持,并被许多程序所依赖,使用 C89 标准编写的预先存在的语言描述。鉴于标准委员会的章程,他们可能打算将其视为他们提到的“流行扩展”之一,支持将是“实施质量”问题,尽管许多实施可能没有特别认为持续支持因为已经明确定义的东西实际上是“扩展”。 【参考方案1】:

具有灵活数组成员的结构不能是数组的元素。这在C standard 的第 6.7.2.1p3 节中有规定:

结构或联合不得包含不完整或 函数类型(因此,结构不应包含 本身,但可能包含指向自身实例的指针),除了 具有多个的结构的最后一个成员 命名成员可能有不完整的数组类型;这样的结构 (以及任何可能递归地包含一个成员的联合 这样的结构)不应是结构或元素的成员 一个数组

这样做的原因是数组索引是通过指向一个内存位置来完成的,该内存位置是结构大小的倍数。但如果结构的大小可变,则无法知道结构的下一个实例在内存中的位置。

在您的特定情况下,车牌号的最大长度并没有那么大,因此只需使用足够大的固定尺寸来容纳它可能包含的任何值。

struct license_plate
    char issuing_province_territory_code[3];
    char number[20];

此外,使用大小为 1 的数组设置灵活数组成员的方式是在它们标准化之前执行此操作的旧方式,它通常被称为“结构黑客”。声明灵活数组成员的现代方式是使用未指定的大小:

struct license_plate_v2 
    char issuing_province_territory_code [3];
    char number[];

sizeof(struct license_plate_v2) 不包括flexible array member

【讨论】:

啊,“结构黑客”似乎正是我想要的。是否符合标准?这似乎与您的参考相矛盾... @ArtB 从技术上讲不是,但由于很多遗留代码都是以标准前的方式编写的,因此它仍然可以在实践中使用。但是,您仍然不能将这样的结构用作数组的成员。 @ArtB 此外,您设置arr_1arr_2arr_3 指针的方式违反了struct licence_plate_v2 可能具有的任何对齐要求。 为什么不呢?如果每个成员具有相同长度的变量索引仍然是可计算的(显然不是[],但我似乎可以通过index_license_plate_v2 解决它) @ArtB 你可能会侥幸成功,因为struct license_plate_v2 的所有成员都有char 对齐,所以可能整个结构具有相同的对齐要求。如果结构包含比 char 更大的东西,它会在关心正确对齐类型的平台上严重崩溃。

以上是关于通过过度分配内存在结构中内联可变长度数组是不是有效?的主要内容,如果未能解决你的问题,请参考以下文章

使用可变长度数组是不是安全?

使用可变长度数组是不是有任何开销?

[精]JAVA数组的内存结构详解

在其内存应该已被释放后访问可变长度数组

scala数据结构与可变不可变

从C中的函数返回具有多个可变长度数组的结构