我可以通过为封闭结构分配更多空间来“过度扩展”数组吗?

时间:2017-05-02 11:20:45

标签: c arrays language-lawyer undefined-behavior flexible-array-member

坦率地说,这样的代码有效还是产生UB?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct __attribute__((__packed__)) weird_struct
{
    int some;
    unsigned char value[1];
};

int main(void)
{
    unsigned char text[] = "Allie has a cat";
    struct weird_struct *ws =
        malloc(sizeof(struct weird_struct) + sizeof(text) - 1);
    ws->some = 5;
    strcpy(ws->value, text);
    printf("some = %d, value = %s\n", ws->some, ws->value);
    free(ws);
    return 0;
}

http://ideone.com/lpByQD

我从来没有想过它会对这样的事情有效,但看起来SystemV消息队列的确如此:see the man page

所以,如果SysV msg队列可以做到这一点,也许我也可以这样做?我想我发现通过网络发送数据非常有用(因此__attribute__((__packed__)))。

或许,这可能是SysV msg队列的特定保证,我不应该在其他地方做类似的事情?或者,也许这种技术可以使用,只有我做错了?我想出来我最好问。

- 1中的这个malloc(sizeof(struct weird_struct) + sizeof(text) - 1)是因为我考虑到unsigned char value[1]因此分配了一个字节,所以我可以从sizeof(text)中减去它。

2 个答案:

答案 0 :(得分:21)

执行此操作的标准C方式(C99 )将使用 flexible array member 。结构的最后一个成员需要是不完整的数组类型,您可以在运行时分配所需的内存量。

这样的东西
struct __attribute__((__packed__)) weird_struct
{
    int some;
    unsigned char value [ ];   //nothing, no 0, no 1, no nothing...
}; 

以后

struct weird_struct *ws =
    malloc(sizeof(struct weird_struct) + strlen("this to be copied") + 1);

struct weird_struct *ws =
    malloc(sizeof(struct weird_struct) + sizeof("this to be copied"));

将完成这项工作。

相关,引用C11标准,章节§6.7.2.1

  

作为一种特殊情况,具有多个命名成员的结构的最后一个元素可以   有一个不完整的数组类型;这称为灵活数组成员。在大多数情况下,   灵活的数组成员被忽略。特别是,结构的大小就像是   省略了灵活的数组成员,除了它可能有更多的尾随填充   遗漏意味着。但是,当.(或->)运算符具有左操作数时   (指向)具有灵活数组成员和右操作数名称的结构   成员,它的行为好像该成员被替换为最长的数组(具有相同的   元素类型)不会使结构大于被访问的对象;该   数组的偏移量应保持灵活阵列成员的偏移量,即使这会有所不同   从替换阵列的。如果此数组没有元素,则表现得好像   它有一个元素,但如果有任何尝试访问它,行为是未定义的   元素或生成一个经过它的指针。

与单元素数组用法相关,来自online gcc manual page for zero-length array support option

struct line {
  int length;
  char contents[0];
};

struct line *thisline = (struct line *)
  malloc (sizeof (struct line) + this_length);
thisline->length = this_length;
     

在ISO C90中,你必须给contents一个长度为1,这意味着要么浪费空间要么将参数复杂化为malloc。

也会回答-1参数中的malloc()部分,因为sizeof(char)在C中保证为1

答案 1 :(得分:1)

如果代码访问超出其声明边界的数组对象,则标准允许实现以他们认为合适的任何方式操作,即使代码拥有将由此访问的存储。据我所知,这条规则旨在允许编译器给出类似的东西:

struct s1 { char arr[4]; char y; } *p;
int x;
...
p->y = 1;
p->arr[x] = 2;
return p->y;

将其视为等同于:

struct s1 { char arr[4]; char y; } *p;
int x;
...
p->arr[x] = 2;
p->y = 1;
return 1;

避免额外的加载步骤,而不必悲观地允许x可能等于4的质量编译器。质量编译器应该能够识别某些涉及访问超出其声明边界的数组的构造(例如涉及指针的那些)以单元素数组作为最后一个元素的结构并合理地处理它们,但是标准中没有任何内容要求它们这样做,并且一些编译器编写者认为应该解释编译器以无意义的方式表现的许可的态度作为邀请这样做。我认为行为将被定义,即使对于x==4情况(意味着编译器必须允许它修改y的可能性),如果数组写入是通过类似的方式处理的:{{ 1}}但标准并不清楚是否需要转换为(char*)(struct s1*)(p->arr)[x] = 2;