c - 灵活的数组成员会导致未定义的行为吗?

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

  1. 通过在结构类型中使用灵活的数组成员 (FAM),我们是否将我们的程序暴露给未定义行为的可能性?

  2. 是否有可能一个程序在使用 FAM 的同时仍然是一个严格符合的程序?

  3. 灵活数组成员的偏移量是否要求在结构的末尾?

这些问题适用于 C99 (TC3)C11 (TC1)

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

int main(void) {
    struct s {
        size_t len;
        char pad;
        int array[];
    };

    struct s *s = malloc(sizeof *s + sizeof *s->array);

    printf("sizeof *s: %zu\n", sizeof *s);
    printf("offsetof(struct s, array): %zu\n", offsetof(struct s, array));

    s->array[0] = 0;
    s->len = 1;

    printf("%d\n", s->array[0]);

    free(s);
    return 0;
}

输出:

sizeof *s: 16
offsetof(struct s, array): 12
0

最佳答案

简短的回答

  1. 是的。使用 FAM 的常见约定使我们的程序面临未定义行为的可能性。话虽如此,我并不知道任何现有的符合规范的实现会出现异常行为。

  2. 可能,但不太可能。即使我们实际上没有达到未定义的行为,我们仍然可能无法严格遵守。

  3. 否。FAM 的偏移量不需要位于结构的末尾,它可以覆盖任何尾随填充字节。

答案适用于 C99 (TC3)C11 (TC1)


长答案

FAM 首次在 C99 (TC0)(1999 年 12 月)中引入,其原始规范要求 FAM 的偏移位于结构的末尾。原始规范定义明确,因此不会导致未定义的行为,也不会成为严格符合性的问题。

C99 (TC0) §6.7.2.1 p16(1999 年 12 月)

[This document is the official standard, it is copyrighted and not freely available]

问题在于常见的 C99 实现(例如 GCC)没有遵循标准的要求,并允许 FAM 覆盖任何尾随填充字节。他们的方法被认为更有效,并且由于他们遵循标准的要求 - 会导致向后兼容性中断,委员会选择更改规范,并且从 C99 TC2(2004 年 11 月)开始,该标准不再需要FAM 的偏移量位于结构的末尾。

C99 (TC2) §6.7.2.1 p16(2004 年 11 月)

[...] the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding than the omission would imply.

新规范删除了要求 FAM 的偏移量位于结构末尾的声明,它引入了一个非常不幸的后果,因为标准允许实现不保留任何填充字节的值的自由在一致状态下的结构或 union 内。更具体地说:

C99 (TC3) §6.2.6.1 p6

When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values.

这意味着如果我们的任何 FAM 元素对应(或覆盖)任何尾随填充字节,则在存储到结构的成员时 - 它们(可能)采用未指定的值。我们甚至不需要考虑这是否适用于存储到 FAM 本身的值,即使严格解释这仅适用于 FAM 以外的成员,也足以造成破坏。

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

int main(void) {
    struct s {
        size_t len;
        char pad;
        int array[];
    };

    struct s *s = malloc(sizeof *s + sizeof *s->array);

    if (sizeof *s > offsetof(struct s, array)) {
        s->array[0] = 123;
        s->len = 1; /* any padding bytes take unspecified values */

        printf("%d\n", s->array[0]); /* indeterminate value */
    }

    free(s);
    return 0;
}

一旦我们存储到结构的成员中,填充字节就会采用未指定的字节,因此对与任何尾随填充字节对应的 FAM 元素值所做的任何假设现在都是错误的。这意味着任何假设都会导致我们无法严格遵守。

未定义的行为

虽然填充字节的值是“未指定的值”,但受它们影响的类型却不能这样说,因为基于未指定值的对象表示可以生成陷阱表示。因此,描述这两种可能性的唯一标准术语是“不确定值”。如果 FAM 的类型恰好有陷阱表示,那么访问它不仅仅是一个未指定的值,而是未定义的行为。

等等,还有更多。如果我们同意描述这种值的唯一标准术语是“不确定值”,那么即使 FAM 的类型恰好没有陷阱表示,我们也达到了未定义的行为,因为 C 的官方解释标准委员会认为将不确定的值传递给标准库函数是未定义的行为。

关于c - 灵活的数组成员会导致未定义的行为吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44745677/

相关文章:

c - 在 C 中从文件中读取时向前看一个字符

c++ - 不同编译器之间的名称查找不一致

c - 为什么这些构造使用前后递增的未定义行为?

c - 解析器代码中的奇怪编译器错误

C 给数组加值? `array + value`

c - C. usleep 的时间延迟

c - c编程中如何使用 "extern struct"共享变量并用gcc编译?

c++ - 标准 C++ 输入输出流如何处理 intXX_t 类型?

c++ - 如何理解#1664的提议决议

c++ - C++ 中的这种未定义行为是从悬空指针调用函数吗