简体   繁体   English

对 char 数组结构成员进行类型双关

[英]type-punning a char array struct member

Consider the following code:考虑以下代码:

typedef struct { char byte; } byte_t;
typedef struct { char bytes[10]; } blob_t;

int f(void) {
  blob_t a = {0};
  *(byte_t *)a.bytes = (byte_t){10};
  return a.bytes[0];
}

Does this give aliasing problems in the return statement?这会在 return 语句中产生别名问题吗? You do have that a.bytes dereferences a type that does not alias the assignment in patch , but on the other hand, the [0] part dereferences a type that does alias.您确实有a.bytes取消引用了一个不为patch中的赋值设置别名的类型,但另一方面, [0]部分取消引用了一个具有别名的类型。

I can construct a slightly larger example where gcc -O1 -fstrict-aliasing does make the function return 0, and I'd like to know if this is a gcc bug, and if not, what I can do to avoid this problem (in my real-life example, the assignment happens in a separate function so that both functions look really innocent in isolation).我可以构建一个稍大的示例,其中gcc -O1 -fstrict-aliasing确实使 function 返回 0,我想知道这是否是 ZE0D511356BD44120AF49CC96C9DCF3我在现实生活中的例子,分配发生在一个单独的 function 中,因此这两个函数在隔离时看起来真的很无辜)。

Here is a longer more complete example for testing:这是一个更长更完整的测试示例:

#include <stdio.h>

typedef struct { char byte; } byte_t;
typedef struct { char bytes[10]; } blob_t;

static char *find(char *buf) {
    for (int i = 0; i < 1; i++) { if (buf[0] == 0) { return buf; }}
    return 0;
}

void patch(char *b) { 
    *(byte_t *) b = (byte_t) {10}; 
}

int main(void) {
    blob_t a = {0};
    char *b = find(a.bytes);
    if (b) {
        patch(b);
    }
    printf("%d\n", a.bytes[0]);
}

Building with gcc -O1 -fstrict-aliasing produces 0使用gcc -O1 -fstrict-aliasing生成0

The main issue here is that those two structs are not compatible types.这里的主要问题是这两个结构不是兼容的类型。 And so there can be various problems with alignment and padding.因此 alignment 和填充可能存在各种问题。

That issue aside, the standard 6.5/7 only allows for this (the "strict aliasing rule"):除了这个问题,标准 6.5/7 只允许这样做(“严格的别名规则”):

An object shall have its stored value accessed only by an lvalue expression that has one of the following types: object 的存储值只能由具有以下类型之一的左值表达式访问:

  • a type compatible with the effective type of the object,与 object 的有效类型兼容的类型,
    ... ...
  • an aggregate or union type that includes one of the aforementioned types among its members在其成员中包含上述类型之一的聚合或联合类型

Looking at *(byte_t *)a.bytes , then a.bytes has the effective type char[10] .查看*(byte_t *)a.bytes ,然后a.bytes具有有效类型char[10] Each individual member of that array has in turn the effective type char .该数组的每个单独成员依次具有有效类型char You de-reference that with byte_t , which is not a compatible struct type nor does it have a char[10] among its members.您使用byte_t取消引用它,它不是兼容的结构类型,也没有char[10]在其成员中。 It does have char though.但它确实有char

The standard is not exactly clear how to treat an object which effective type is an array.该标准并不清楚如何处理有效类型为数组的 object。 If you read the above part strictly, then your code does indeed violate strict aliasing, because you access a char[10] through a struct which doesn't have a char[10] member.如果您严格阅读上述部分,那么您的代码确实违反了严格的别名,因为您通过没有char[10]成员的结构访问char[10] [10]。 I'd also be a bit concerned about the compiler padding either struct to meet alignment.我也有点担心编译器填充任一结构以满足 alignment。

Generally, I'd simply advise against doing fishy things like this.一般来说,我只是建议不要做这样的可疑事情。 If you need type punning, then use a union.如果您需要类型双关语,请使用联合。 And if you wish to use raw binary data, then use uint8_t instead of the potentially signed & non-portable char .如果您希望使用原始二进制数据,请使用uint8_t而不是可能已签名且不可移植的char

The error is in *(byte_t *)a.bytes = (byte_t){10};错误在*(byte_t *)a.bytes = (byte_t){10}; . . The C spec has a special rule about character types (6.5§7), but that rule only applies when using character type to access any other type, not when using any type to access a character. C 规范有一个关于字符类型的特殊规则(6.5§7),但该规则仅适用于使用字符类型访问任何其他类型时,而不适用于使用任何类型访问字符时。

According to the Standard, the syntax array[index] is shorthand for *((array)+(index)) .根据标准,语法array[index]*((array)+(index))的简写。 Thus, p->array[index] is equivalent to *((p->array) + (index)) , which uses the address of p to compute the address of p->array , and then without regard for p 's type, adds index (scaled by the size of the array-element type), and then dereferences the resulting pointer to yield an lvalue of the array-element type.因此, p->array[index]等价于*((p->array) + (index)) ,它使用p的地址来计算p->array的地址,然后不考虑p的类型,添加index (按数组元素类型的大小缩放),然后取消引用结果指针以产生数组元素类型的左值。 Nothing in the wording of the Standard would imply that an access via the resulting lvalue is an access to an lvalue of the underlying structure type.标准的措辞中没有任何内容暗示通过结果左值的访问是对底层结构类型的左值的访问。 Thus, if the struct member is an array of character type, the constraints of N1570 6.5p7 would allow an lvalue of that form to access storage of any type.因此,如果结构成员是字符类型的数组,N1570 6.5p7 的约束将允许该形式的左值访问任何类型的存储。

The maintainers of some compilers such as gcc, however, appear to view the laxity of the Standard there as a defect.然而,gcc 等一些编译器的维护者似乎将那里标准的松懈视为缺陷。 This can be demonstrated via the code:这可以通过代码来证明:

struct s1 { char x[10]; };
struct s2 { char x[10]; };
union s1s2 { struct s1 v1; struct s2 v2; } u;

int read_s1_x(struct s1 *p, int i)
{
    return p->x[i];
}
void set_s2_x(struct s2 *p, int i, int value)
{
    p->x[i] = value;
}
__attribute__((noinline))
int test(void *p, int i)
{
    if (read_s1_x(p, 0))
        set_s2_x(p, i, 2);
    return read_s1_x(p, 0);
}
#include <stdio.h>
int main(void)
{
    u.v2.x[0] = 1;
    int result = test(&u, 0);
    printf("Result = %d / %d", result, u.v2.x[0]);
}

The code abides the constraints in N1570 6.5p7 because it all accesses to any portion of u are performed using lvalues of character type.该代码遵守 N1570 6.5p7 中的约束,因为它对u的任何部分的所有访问都是使用字符类型的左值执行的。 Nonetheless, the code generated by gcc will not allow for the possibility that the storage accessed by (*(struct s1))->x[0] might also be accessed by (*(struct s2))->x[i] despite the fact that both accesses use lvalues of character type.尽管如此,由 gcc 生成的代码将不允许(*(struct s1))->x[0]访问的存储也可能被(*(struct s2))->x[i]访问,尽管两种访问都使用字符类型的左值这一事实。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM