24

坦率地说,这样的代码是有效的还是会产生 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 消息队列正是这样做的:参见手册页

那么,如果 SysV 消息队列可以做到这一点,也许我也可以做到这一点?我想我会发现这对于通过网络发送数据很有用(因此是__attribute__((__packed__)))。

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

- 1malloc(sizeof(struct weird_struct) + sizeof(text) - 1)因为我考虑到无论如何都分配了一个字节,unsigned char value[1]所以我可以从sizeof(text).

4

2 回答 2

20

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

就像是

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

作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能具有不完整的数组类型;这称为灵活数组成员。在大多数情况下,灵活数组成员被忽略。特别是,结构的大小就像省略了柔性数组成员一样,只是它可能具有比省略所暗示的更多的尾随填充。然而,当一个.(或->) 运算符的左操作数是(指向)具有灵活数组成员的结构,右操作数命名该成员,它的行为就好像该成员被替换为最长的数组(具有相同的元素类型),不会使结构大于被访问的对象;数组的偏移量应保持灵活数组成员的偏移量,即使这与替换数组的偏移量不同。如果这个数组没有元素,它的行为就好像它有一个元素,但如果尝试访问该元素或生成一个越过它的指针,则行为是不确定的。


与一元素数组使用相关,来自在线 gcc 手册页,用于零长度数组支持选项

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部分,正如在 C中所保证的那样。malloc()sizeof(char)1

于 2017-05-02T11:26:49.163 回答
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(char*)(struct s1*)(p->arr)[x] = 2;但标准并不清楚是否struct s1*需要强制转换。

于 2017-05-02T18:24:40.593 回答