【问题标题】:Undefined behavior with type casting?类型转换的未定义行为?
【发布时间】:2016-06-04 15:19:13
【问题描述】:

举个例子:

typedef struct array_struct {
    unsigned char* pointer;
    size_t length;
} array;

typedef struct vector_struct {
    unsigned char* pointer;
    // Reserved is the amount of allocated memory not being used.
    // MemoryLength = length + reserved;
    size_t length, reserved;
} vector;


// Example Usage:
vector* vct = (vector*) calloc(sizeof(vector), 1);
vct->reserved = 0;
vct->length = 24;
vct->pointer = (unsigned char*) calloc(arr->length, 1);

array* arr = (array*) vct;
printf("%i", arr->length);
free(arr->pointer);
free(arr);

C 似乎按照它们在结构中定义的顺序为结构成员分配内存。这意味着如果您投射vector -> array,如果您对array 执行操作,您仍然会得到相同的结果,就像您对vector 执行操作一样,因为它们具有相同的成员和成员顺序。

只要你只从vector -> array 向下转换,就好像arrayvector 的泛型类型,你就不应该遇到任何问题。

尽管类型结构相似,但这是未定义的不良行为吗?

【问题讨论】:

  • 您假设 arrayvector 具有相同的填充,我认为这不能保证。
  • 有一些关于 SO 的相关问题,但没有一致的答案:Some 称之为未定义行为,others 称之为定义明确的 C 代码。
  • @RobinKrahl 第二个问题的第三个答案说不是。它引用了一篇关于严格别名规则的文章。
  • @FatalSleep 我想答案是特定于编译器的。 gcc 是开源的,所以原则上,是的,你可以发现它是如何填充的。
  • 我也希望在大多数编译器中它会做你所期望的。很难想象一个 CPU 架构需要根据后面的成员对早期的结构成员进行不同的对齐/填充。

标签: c struct memory-address type-punning


【解决方案1】:

如果您允许类型别名(C 不允许,但大多数编译器允许,默认情况下或通过某些编译标志),这是明确定义的行为,如果您禁止这种类型别名(这通常称为“严格别名”,因为规则非常严格)。来自 C 标准的 N1570 草案:

6.5.2.3

6 一个特殊的保证是为了简化联合的使用:如果联合包含几个共享相同初始序列的结构(见下文),并且联合对象当前包含一个在这些结构中,允许检查其中任何一个的公共初始部分,只要可以看到联合类型的完整声明。 如果对应的成员对于一个或多个初始成员的序列具有兼容的类型(并且对于位域,具有相同的宽度),则两个结构共享一个共同的初始序列。

该部分是关于联合的,但为了使该行为在联合中合法,它限制了填充的可能性,因此要求两个结构共享一个公共布局和初始填充。所以我们已经为我们准备好了。

现在,对于严格的别名,标准说:

6.5

7 对象的存储值只能由具有以下类型之一的左值表达式访问:

  • 与对象的有效类型兼容的类型
  • [...]

“兼容类型”是:

6.2.7

1 如果类型相同,则两种类型具有兼容类型。

它继续解释更多,并列出了一些有更多“回旋余地”的案例,但没有一个适用于此。对你来说不幸的是,降压在这里停止。这是未定义的行为。

现在,您可以做的一件事是:

typedef struct array_struct {
    unsigned char* pointer;
    size_t length;
} array;

typedef struct vector_struct {
    array array;
    size_t reserved;
} vector;

【讨论】:

  • 我只是想发布相同的报价,尽管我不同意我们可以推断出 OP 所做的事情是明确定义的。但我同意它应该在实践中起作用。
  • 肉在这里“一个或多个”。
  • “我应该补充一点,严格的别名规则可能会禁止这样做,这可能只能通过联合来完成,但我不确定。”这是正确的。仅仅因为结构在内存中具有相同的布局并不意味着编译器认为类型是可互换的。它们仍然是不同的类型,并且通过指向不同类型的指针访问一种类型仍然是未定义的。让很多人感到困惑的是,类型系统的规则比内存中存在的规则或内存的对齐方式更高。
  • @FatalSleep:主要引入了严格的别名规则,以便编译器可以进行非常积极的优化。如果没有严格的别名,编译器必须从内存中进行更多的加载,因为有可能两个对象对同一个内存进行别名,并且通过一个对象进行的修改将影响对另一个对象的下一次读取。但是严格的别名让编译器假设两个不同类型的对象引用不同的内存位置,因此如果一个对象被修改,则必须完成更少的加载。你仍然可以在两者之间memcpy,但是违反严格别名会导致编译器...
  • ...进行“无效”优化,完全消除您的程序及其预期行为。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 2022-10-26
  • 2020-03-17
  • 1970-01-01
  • 1970-01-01
  • 2016-11-16
相关资源
最近更新 更多