【问题标题】:Can I "over-extend" an array by allocating more space to the enclosing struct?我可以通过为封闭结构分配更多空间来“过度扩展”数组吗?
【发布时间】:2017-05-02 11:20:45
【问题描述】:

坦率地说,这样的代码是有效的还是会产生 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 消息队列可以做到这一点,也许我也可以做到这一点?我想我会发现这对通过网络发送数据很有用(因此是 __attribute__((__packed__)))。

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

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

【问题讨论】:

  • @IharobAlAsimi 因为sizeof(struct weird_struct) 中计算了一个字节,因为unsigned char value[1] 的大小无论如何都是一个字节?至少我是这么想的。
  • @IharobAlAsimi,看起来确实如此。虽然我同意您使用 strlen() 的建议。
  • 访问超出其声明限制的数组会调用未定义的行为。它适用于大多数编译器,但使用灵活的数组成员编译器可能会更严格地处理它;不要依赖上面的代码来工作。
  • @Olaf 因此,我认为 POSIX 必须 规定这不是 UB,否则如果不调用 POSIX 提到的 SysV msg 队列,就不可能UB?这是正确的吗?
  • 原始arr[1] struct hack 是否会引发UB,当结构分配时没有声明类型(例如通过malloc),取决于“对象”一词的确切含义C标准,从来没有得到任何人满意的解决;尽管不断尝试修正措辞,但案文仍然自相矛盾,并且仍然至少有三种解释具有很强的“预期”含义;在其中两个之下是 UB,但在第三个之下不是。

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


【解决方案1】:

执行此操作的标准 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 章

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


与单元素数组用法有关,来自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 的参数复杂化。

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

【讨论】:

    【解决方案2】:

    如果代码访问数组对象超出其规定的界限,标准允许实现以他们认为合适的任何方式行事,即使代码拥有由此访问的存储空间。据我所知,这条规则旨在允许编译器给出如下内容:

    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-&gt;arr)[x] = 2; 但标准关于是否有必要强制转换为struct s1* 并不清楚。

    【讨论】:

    • 标准允许实现以任何他们认为合适的方式行动另一种说法:未定义的行为。
    • @2501:该标准没有努力定义使编译器适合特定用途的所有行为。标准允许 实现在特定情况下以荒谬的方式行事这一事实并不意味着这种行为不会使编译器不适用于许多目的。所有可能的符合 C 实现的任务都可以执行的任务相对较少,因此很少有程序可以在所有 C 实现上表现得很好。大多数程序只能在合适的实现上运行......
    • ...满足他们的需求。特定程序不能在特定实现上有用地运行这一事实并不意味着任何一个都有缺陷,而只是该实现不适合与该程序一起使用。我不确定为什么一些编译器作者将 UB 定义中的短语“不可移植或错误”解释为简单的“错误”,但这似乎是流行的宗教。
    • @2501:换一种说法,如果 90% 的实现,包括适用于某种目的的所有实现,指定了某个特定操作的行为,那么在这 90% 的实现上,该操作将具有定义的行为,无论标准是否强制。标准没有为某些操作定义行为这一事实并不意味着该行为在所有上下文中都是未定义的。
    猜你喜欢
    • 1970-01-01
    • 2011-05-03
    • 1970-01-01
    • 2012-07-29
    • 1970-01-01
    • 2011-08-21
    • 2019-06-06
    • 1970-01-01
    • 2011-01-31
    相关资源
    最近更新 更多