警告的后果“取消引用类型-punned指针将打破严格的别名规则”

时间:2021-07-10 02:14:29

I have gone through some queries on the similar topic and some material related to it. But my query is mainly to understand the warning for the below code. I do not want a fix !! I understand there are two ways, a union or using memcpy.

我已经对类似的主题和相关的资料进行了一些查询。但是我的查询主要是为了理解下面代码的警告。我不想修理!!我知道有两种方法,联合或使用memcpy。

uint32 localval;
void * DataPtr;
localval = something;
(*(float32*)(DataPtr))= (*(const float32*)((const void*)(&localval)));

please note the below significant points
1. both the types involved in the cast here are 32 bit. (or am i wrong ?)
2. Both are local variables.

请注意以下要点1。这里涉及的类型转换都是32位的。(我说错了吗?)都是局部变量。

Compiler specific points:
1. The code is supposed to be platform independent, this is a requirement!!
2. I compiled on GCC and it just worked as expected. (I could reinterpret the int as a float) , which is why i ignored the warning.

编译器具体点:1。代码应该是平*立的,这是一个要求!2。我编译了GCC,它只是按照预期工作。(我可以将int重新解释为一个浮点数),这就是我忽略警告的原因。

My questions
1. What optimizations could the compiler perform in this aliasing case ?
2. As both would occupy the same size (correct me if not) what could be the side affects of such a compiler optimization ?
3. Can I safely ignore the warning or turn off aliasing ?
4. If the compiler hasn't performed an optimization and my program is not broken after my first compilation ? Can i safely assume that every time the compiler would behave the same way (does not do optimizations) ?
5. Does the aliasing apply to a void * typecast too ? or is it applicable only for the standard typecasts (int,float etc...) ?
6. what are the affects if I disable the aliasing rules ?

我的问题1。在这种混叠情况下,编译器可以执行什么优化?2。由于两者都占用相同的大小(如果没有的话,请纠正我),那么这种编译器优化的副作用是什么呢?3所示。我是否可以安全地忽略警告或关闭混叠?4所示。如果编译器没有执行优化,我的程序在我第一次编译后没有被破坏?我是否可以安全地假设每次编译器的行为都是相同的(不做优化)?5。别名是否也适用于void *类型转换?还是只适用于标准类型(int,float等)?6。如果我禁用混叠规则会有什么影响?

Edited
1. based on R's and Matt McNabb's corrections
2. added a new questions

编辑1。基于R和Matt McNabb的《纠正2》。添加了一个新的问题

3 个解决方案

#1


3  

Language standards try to strike a balance between the sometimes competing interests of programmers that will use the language and compiler writers that want to use a broad set of optimizations to generate reasonably fast code. Keeping variables in registers is one such optimization. For variables that are "live" in a section of a program the compiler tries to allocate them in registers. Storing at the address in a pointer could store anywhere in the program's address space - which would invalidate every single variable in a register. Sometimes the compiler could analyze a program and figure out where a pointer could or could not be pointing, but the C (and C++) language standards consider this an undue burden, and for "system" type of programs often an impossible task. So the language standards relax the constraints by specifying that certain constructs lead to "undefined behavior" so the compiler writer can assume they don't happen and generate better code under that assumption. In the case of strict aliasing the compromise reached is that if you store to memory using one pointer type, then variables of a different type are assumed to be unchanged, and thus can be kept in registers, or stores and loads to these other types can be reordered with respect to the pointer store.

语言标准试图在程序员有时会相互竞争的兴趣和编译器编写者之间取得平衡,后者希望使用广泛的优化集来生成合理快速的代码。在寄存器中保存变量就是这样一种优化。对于“live”的变量,编译器试图将它们分配到寄存器中。存储在一个指针中的地址可以存储在程序的地址空间中的任何位置——这将使寄存器中的每一个变量无效。有时,编译器可以分析一个程序,并找出一个指针可以指向什么地方,或者不能指向什么地方,但是C(和c++)语言标准认为这是一个不必要的负担,对于“系统”类型的程序来说,这通常是一项不可能完成的任务。因此,语言标准通过指定某些构造导致“未定义行为”来放松约束,因此编译器编写者可以假设它们不会发生,并在该假设下生成更好的代码。在严格的混叠的情况下达成的妥协是,如果你使用一个指针类型存储到内存,然后不同类型的变量假定不变,因此可以保存在寄存器,或商店和负载可以重新排序这些其他类型的指针存储。

There are many examples of these kind of optimizations in this paper "Undefined Behavior: What Happened to My Code?"

在本文“未定义行为:我的代码发生了什么?”

http://pdos.csail.mit.edu/papers/ub:apsys12.pdf

http://pdos.csail.mit.edu/papers/ub apsys12.pdf

There is an example there of a violation of the strict-aliasing rule in the Linux kernel, apparently the kernel avoids the problem by telling the compiler not to make use of the strict-aliasing rule for optimizations "The Linux kernel uses -fno-strict-aliasing to disable optimizations based on strict aliasing."

有一个违反Linux内核中严格别名规则的例子,显然内核通过告诉编译器不要使用严格别名规则来优化,从而避免了这个问题。

struct iw_event {
    uint16_t len; /* Real length of this stuff */
    ...
};
static inline char * iwe_stream_add_event(
    char * stream, /* Stream of events */
    char * ends, /* End of stream */
    struct iw_event *iwe, /* Payload */
    int event_len ) /* Size of payload */
{
    /* Check if it's possible */
    if (likely((stream + event_len) < ends)) {
        iwe->len = event_len;
        memcpy(stream, (char *) iwe, event_len);
        stream += event_len;
    }
    return stream;
}

Figure 7: A strict aliasing violation, in include/net/iw_handler.h of the Linux kernel, which uses GCC’s -fno-strict-aliasing to prevent possible reordering.

图7:在include/net/iw_handler中存在严格的别名冲突。Linux内核的h,它使用GCC的-fno-strict- aliation来防止可能的重新排序。

2.6 Type-Punned Pointer Dereference

2.6 Type-Punned指针

C gives programmers the freedom to cast pointers of one type to another. Pointer casts are often abused to reinterpret a given object with a different type, a trick known as type-punning. By doing so, the programmer expects that two pointers of different types point to the same memory location (i.e., aliasing). However, the C standard has strict rules for aliasing. In particular, with only a few exceptions, two pointers of different types do not alias [19, 6.5]. Violating strict aliasing leads to undefined behavior. Figure 7 shows an example from the Linux kernel. The function first updates iwe->len, and then copies the content of iwe, which contains the updated iwe->len, to a buffer stream using memcpy. Note that the Linux kernel provides its own optimized memcpy implementation. In this case, when event_len is a constant 8 on 32-bit systems, the code expands as follows.

C允许程序员将一种类型的指针转换为另一种类型的指针。指针类型转换常常被滥用来重新解释具有不同类型的给定对象,这种技巧称为类型punning。通过这样做,程序员期望不同类型的两个指针指向相同的内存位置(即:别名)。然而,C标准有严格的混叠规则。特别地,除了少数例外,两个不同类型的指针不会别名[19,6.5]。违反严格的混叠会导致未定义的行为。图7显示了一个来自Linux内核的示例。该函数首先更新iwe->len,然后复制iwe的内容,其中包含更新的iwe->len,并使用memcpy进行缓冲流。注意,Linux内核提供了自己的优化memcpy实现。在这种情况下,当event_len是32位系统中的常数8时,代码展开如下。

iwe->len = 8;
*(int *)stream = *(int *)((char *)iwe);
*((int *)stream + 1) = *((int *)((char *)iwe) + 1);

The expanded code first writes 8 to iwe->len, which is of type uint16_t, and then reads iwe, which points to the same memory location of iwe->len, using a different type int. According to the strict aliasing rule, GCC concludes that the read and the write do not happen at the same memory location, because they use different pointer types, and reorders the two operations. The generated code thus copies a stale iwe->len value. The Linux kernel uses -fno-strict-aliasing to disable optimizations based on strict aliasing.

扩展代码首先8到这个- >兰写道,这是uint16_t类型,然后读这个,它指向同一个内存地址的这个- > len,使用不同类型int。根据严格的混叠规则,GCC总结说,读和写在同一内存位置不会发生,因为他们使用不同的指针类型,重新排列了两个操作。因此,生成的代码将复制一个过时的iwe->len值。Linux内核使用-fno-strict-别名来禁用基于严格别名的优化。

Answers

答案

1) What optimizations could the compiler perform in this aliasing case ?

1)在这种混叠情况下,编译器可以执行哪些优化?

The language standard is very specific about the semantics (behavior) of a strictly conforming program - the burden is on the compiler writer or language implementor to get it right. Once the programmer crosses the line and invokes undefined behavior then the standard is clear that the burden of proof that this will work as intended falls on the programmer, not on the compiler writer - the compiler in this case has been nice enough to warn that undefined behavior has been invoked although it is under no obligation to even do that. Sometimes annoyingly people will tell you that at this point "anything can happen" usually followed by some joke/exaggeration. In the case of your program the compiler could generate code that is "typical for the platform" and store to localval the value of something and then load from localval and store at DataPtr, like you intended, but understand that it is under no obligation to do so. It sees the store to localval as a store to something of uint32 type and it sees the dereference of the load from (*(const float32*)((const void*)(&localval))) as a load from a float32 type and concludes these aren't to the same location so localval can be in a register containing something while it loads from an uninitialized location on the stack reserved for localval should it decide it needs to "spill" that register back to its reserved "automatic" storage (stack). It may or may not store localval to memory before dereferencing the pointer and loading from memory. Depending on what follows in your code it may decide that localval isn't used and the assignment of something has no side-effect, so it may decide that assignment is "dead code" and not even do the assignment to a register.

语言标准对严格遵循的程序的语义(行为)非常具体——编译器编写器或语言实现者的责任是使其正确。一旦程序员越界并调用未定义行为的标准是明确的举证责任,这将按预期工作的程序员,而不是在编译器作者——在这种情况下,编译器已经好足够的警告说,未定义的行为已经被调用,虽然它甚至没有义务这样做。有时令人恼火的是,人们会告诉你,在这一点上“任何事情都有可能发生”,之后通常会有一些笑话或夸张。在您的程序中,编译器可以生成“平台的典型代码”,并将某些内容的值存储到localval中,然后像您希望的那样从localval加载并存储到DataPtr中,但是要知道它没有义务这么做。它把商店localval uint32存储东西的类型和它把废弃的负载(*(const float32 *)((const void *)(&localval)))作为负载从float32类型和总结这些不是相同的位置所以localval可以在寄存器包含未初始化的位置虽然它加载的东西在堆栈上留给localval应该决定它需要登记“泄漏”回到了保留“自动”存储(栈)。在取消引用指针并从内存加载之前,它可以或不可以将localval存储到内存中。根据代码中的内容,它可能会决定不使用localval,并且赋值没有副作用,因此它可能会决定赋值是“死代码”,甚至不会对寄存器执行赋值。

2) As both would occupy the same size (correct me if not) what could be the side affects of such a compiler optimization ?

2)由于两者都占用相同的大小(如果没有的话,请纠正我)这样的编译器优化的副作用是什么?

The effect could be that an undefined value is stored at the address pointed to by DataPtr.

结果可能是一个未定义的值存储在DataPtr指定的地址。

3) Can I safely ignore the warning or turn off aliasing ?

3)我能安全忽略警告或关闭别名吗?

That is specific to the compiler you are using - if the compiler documents a way to turn off the strict aliasing optimizations then yes, with whatever caveats the compiler makes.

这是特定于您正在使用的编译器的—如果编译器记录了一种关闭严格的别名优化的方法,那么是的,无论编译器发出什么警告。

4) If the compiler hasn't performed an optimization and my program is not broken after my first compilation ? Can i safely assume that every time the compiler would behave the same way (does not do optimizations) ?

4)如果编译器没有执行优化,我的程序在第一次编译后没有被破坏?我是否可以安全地假设每次编译器的行为都是相同的(不做优化)?

Maybe, sometimes very small changes in another part of your program could change what the compiler does to this code, think for a moment if the function is "inlined" it could be thrown in the mix of some other part of your code, see this SO question.

也许,有时候你的程序的另一部分中很小的改变就能改变编译器对这段代码所做的事情,想想如果这个函数是“内联的”它可能会被扔进你代码的其他部分中,看看这个问题。

5) Does the aliasing apply to a void * typecast too ? or is it applicable only for the standard typecasts (int,float etc...) ?

5)别名是否也适用于void * typecast ?还是只适用于标准类型(int,float等)?

You cannot dereference a void * so the compiler just cares about the type of your final cast (and in C++ it would gripe if you convert a const to non-const and vice-versa).

您不能取消对void *的引用,因此编译器只关心您的最终强制类型(在c++中,如果您将const转换为non-const,反之亦然)。

6) what are the affects if I disable the aliasing rules ?

6)如果我禁用混叠规则会有什么影响?

See your compiler's documentation - in general you will get slower code, if you do this (like the Linux kernel chose to do in the example from the paper above) then limit this to a small compilation unit, with only the functions where this is necessary.

请参阅编译器的文档——一般来说,如果您这样做的话,您将会得到更慢的代码(就像上面文章中的Linux内核所选择的那样),然后将其限制为一个小的编译单元,只有必要的函数。

Conclusion

结论

I understand your questions are for curiosity and trying to better understand how this works (or might not work). You mentioned it is a requirement that the code be portable, by implication then it is a requirement that the program be compliant and not invoke undefined behavior (remember, the burden is on you if you do). In this case, as you pointed out in the question, one solution is to use memcpy, as it turns out not only does that make your code compliant and therefore portable, it also does what you intend in the most efficient way possible on current gcc with optimization level -O3 the compiler converts the memcpy into a single instruction storing the value of localval at the address pointed to by DataPtr, see it live in coliru here - look for the movl %esi, (%rdi) instruction.

我理解你的问题是出于好奇,并试图更好地理解它是如何工作的(或者可能不工作)。您提到过,要求代码是可移植的,这意味着,要求程序是兼容的,而不是调用未定义的行为(请记住,如果您这么做的话,负担就在您身上)。在这种情况下,当你指出的问题,一个解决方案是使用memcpy,结果是不仅使代码兼容的,因此便携,它也会按照你的意愿移动以最有效的方式可能与优化级别o3对当前gcc编译器将memcpy转换成一个单指令存储localval的价值指向的地址DataPtr,看到这里住在coliru——寻找movl % esi,(% rdi)指令。

#2


3  

You have an incomplete example (as written, it exhibits UB since localval is uninitialized) so let me complete it:

您有一个不完整的示例(正如所写的,由于localval未初始化,它显示了UB),所以让我来完成它:

uint32 localval;
void * DataPtr;
DataPtr = something;
localval = 42;
(*(float32*)(DataPtr))= (*(const float32*)((const void*)(&localval)));

Now, since localval has type uint32 and *(const float32*)((const void*)(&localval)) has type float32, they cannot alias, so the compiler is free to reorder the last two statements with respect to each other. This would obviously result in behavior different from what you want.

现在,由于localval有类型uint32和*(const float32*)(const void*)(&localval))有类型float32,它们不能别名,因此编译器可以*地对最后两个语句进行重排序。这显然会导致不同于你想要的行为。

The correct way to write this is:

正确的写法是:

memcpy(DataPtr, &localval, sizeof localval);

#3


2  

The const makes no difference. To check if the types are the same size, you can compare sizeof (uint32) to sizeof (float32). It's also possible that the two types have differing alignment requirements.

这没什么区别。要检查类型是否相同,可以比较sizeof (uint32)和sizeof (float32)。这两种类型也可能有不同的对齐需求。

Those things aside; the behaviour is undefined to read the memory of localval as if it had a float stored in it, that's what the strict aliasing rules say.

这些事情放在一边;这种行为没有定义为读取localval的内存,就好像其中存储了一个浮点数一样,这就是严格的别名规则所说的。

6.5#6:

6.5 # 6:

The effective type of an object for an access to its stored value is the declared type of the object, if any.

访问其存储值的对象的有效类型是对象的声明类型(如果有的话)。

6.5#7:

6.5 # 7:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types

对象只能通过具有以下类型之一的lvalue表达式访问其存储值

localval has effective type uint32 , and the list of "the following types" doesn't include float32 so this is a violation of the aliasing rules.

localval具有有效类型uint32,并且“以下类型”的列表不包括float32,因此这违反了混叠规则。

If you were aliasing in dynamically allocated memory, then it is different. There's no "declared type", so the "effective type" is whatever was last stored in the object. You could malloc(sizeof (uint32)), and then store a float32 in it and read it back.

如果你在动态分配的内存中混叠,那么它是不同的。没有“已声明的类型”,所以“有效类型”是最后存储在对象中的内容。您可以malloc(sizeof (uint32)),然后在其中存储一个float32并读取它。

To sum up, you seem to be asking "I know this is undefined, but can I rely on my compiler successfully doing it?" To answer that question you will have to specify what your compiler is, and what switches you are invoking it with, at least.

总而言之,您似乎在问:“我知道这是未定义的,但是我可以依赖我的编译器成功地完成它吗?”要回答这个问题,您必须指定编译器是什么,以及您要调用它的什么开关。

Of course there is also the option of adjusting your code so it does not violate the strict-aliasing rules, but you haven't provided enough background info to proceed down this track.

当然,也有调整代码的选项,这样它就不会违反严格的混叠规则,但是您还没有提供足够的背景信息来进行这个跟踪。

#1


3  

Language standards try to strike a balance between the sometimes competing interests of programmers that will use the language and compiler writers that want to use a broad set of optimizations to generate reasonably fast code. Keeping variables in registers is one such optimization. For variables that are "live" in a section of a program the compiler tries to allocate them in registers. Storing at the address in a pointer could store anywhere in the program's address space - which would invalidate every single variable in a register. Sometimes the compiler could analyze a program and figure out where a pointer could or could not be pointing, but the C (and C++) language standards consider this an undue burden, and for "system" type of programs often an impossible task. So the language standards relax the constraints by specifying that certain constructs lead to "undefined behavior" so the compiler writer can assume they don't happen and generate better code under that assumption. In the case of strict aliasing the compromise reached is that if you store to memory using one pointer type, then variables of a different type are assumed to be unchanged, and thus can be kept in registers, or stores and loads to these other types can be reordered with respect to the pointer store.

语言标准试图在程序员有时会相互竞争的兴趣和编译器编写者之间取得平衡,后者希望使用广泛的优化集来生成合理快速的代码。在寄存器中保存变量就是这样一种优化。对于“live”的变量,编译器试图将它们分配到寄存器中。存储在一个指针中的地址可以存储在程序的地址空间中的任何位置——这将使寄存器中的每一个变量无效。有时,编译器可以分析一个程序,并找出一个指针可以指向什么地方,或者不能指向什么地方,但是C(和c++)语言标准认为这是一个不必要的负担,对于“系统”类型的程序来说,这通常是一项不可能完成的任务。因此,语言标准通过指定某些构造导致“未定义行为”来放松约束,因此编译器编写者可以假设它们不会发生,并在该假设下生成更好的代码。在严格的混叠的情况下达成的妥协是,如果你使用一个指针类型存储到内存,然后不同类型的变量假定不变,因此可以保存在寄存器,或商店和负载可以重新排序这些其他类型的指针存储。

There are many examples of these kind of optimizations in this paper "Undefined Behavior: What Happened to My Code?"

在本文“未定义行为:我的代码发生了什么?”

http://pdos.csail.mit.edu/papers/ub:apsys12.pdf

http://pdos.csail.mit.edu/papers/ub apsys12.pdf

There is an example there of a violation of the strict-aliasing rule in the Linux kernel, apparently the kernel avoids the problem by telling the compiler not to make use of the strict-aliasing rule for optimizations "The Linux kernel uses -fno-strict-aliasing to disable optimizations based on strict aliasing."

有一个违反Linux内核中严格别名规则的例子,显然内核通过告诉编译器不要使用严格别名规则来优化,从而避免了这个问题。

struct iw_event {
    uint16_t len; /* Real length of this stuff */
    ...
};
static inline char * iwe_stream_add_event(
    char * stream, /* Stream of events */
    char * ends, /* End of stream */
    struct iw_event *iwe, /* Payload */
    int event_len ) /* Size of payload */
{
    /* Check if it's possible */
    if (likely((stream + event_len) < ends)) {
        iwe->len = event_len;
        memcpy(stream, (char *) iwe, event_len);
        stream += event_len;
    }
    return stream;
}

Figure 7: A strict aliasing violation, in include/net/iw_handler.h of the Linux kernel, which uses GCC’s -fno-strict-aliasing to prevent possible reordering.

图7:在include/net/iw_handler中存在严格的别名冲突。Linux内核的h,它使用GCC的-fno-strict- aliation来防止可能的重新排序。

2.6 Type-Punned Pointer Dereference

2.6 Type-Punned指针

C gives programmers the freedom to cast pointers of one type to another. Pointer casts are often abused to reinterpret a given object with a different type, a trick known as type-punning. By doing so, the programmer expects that two pointers of different types point to the same memory location (i.e., aliasing). However, the C standard has strict rules for aliasing. In particular, with only a few exceptions, two pointers of different types do not alias [19, 6.5]. Violating strict aliasing leads to undefined behavior. Figure 7 shows an example from the Linux kernel. The function first updates iwe->len, and then copies the content of iwe, which contains the updated iwe->len, to a buffer stream using memcpy. Note that the Linux kernel provides its own optimized memcpy implementation. In this case, when event_len is a constant 8 on 32-bit systems, the code expands as follows.

C允许程序员将一种类型的指针转换为另一种类型的指针。指针类型转换常常被滥用来重新解释具有不同类型的给定对象,这种技巧称为类型punning。通过这样做,程序员期望不同类型的两个指针指向相同的内存位置(即:别名)。然而,C标准有严格的混叠规则。特别地,除了少数例外,两个不同类型的指针不会别名[19,6.5]。违反严格的混叠会导致未定义的行为。图7显示了一个来自Linux内核的示例。该函数首先更新iwe->len,然后复制iwe的内容,其中包含更新的iwe->len,并使用memcpy进行缓冲流。注意,Linux内核提供了自己的优化memcpy实现。在这种情况下,当event_len是32位系统中的常数8时,代码展开如下。

iwe->len = 8;
*(int *)stream = *(int *)((char *)iwe);
*((int *)stream + 1) = *((int *)((char *)iwe) + 1);

The expanded code first writes 8 to iwe->len, which is of type uint16_t, and then reads iwe, which points to the same memory location of iwe->len, using a different type int. According to the strict aliasing rule, GCC concludes that the read and the write do not happen at the same memory location, because they use different pointer types, and reorders the two operations. The generated code thus copies a stale iwe->len value. The Linux kernel uses -fno-strict-aliasing to disable optimizations based on strict aliasing.

扩展代码首先8到这个- >兰写道,这是uint16_t类型,然后读这个,它指向同一个内存地址的这个- > len,使用不同类型int。根据严格的混叠规则,GCC总结说,读和写在同一内存位置不会发生,因为他们使用不同的指针类型,重新排列了两个操作。因此,生成的代码将复制一个过时的iwe->len值。Linux内核使用-fno-strict-别名来禁用基于严格别名的优化。

Answers

答案

1) What optimizations could the compiler perform in this aliasing case ?

1)在这种混叠情况下,编译器可以执行哪些优化?

The language standard is very specific about the semantics (behavior) of a strictly conforming program - the burden is on the compiler writer or language implementor to get it right. Once the programmer crosses the line and invokes undefined behavior then the standard is clear that the burden of proof that this will work as intended falls on the programmer, not on the compiler writer - the compiler in this case has been nice enough to warn that undefined behavior has been invoked although it is under no obligation to even do that. Sometimes annoyingly people will tell you that at this point "anything can happen" usually followed by some joke/exaggeration. In the case of your program the compiler could generate code that is "typical for the platform" and store to localval the value of something and then load from localval and store at DataPtr, like you intended, but understand that it is under no obligation to do so. It sees the store to localval as a store to something of uint32 type and it sees the dereference of the load from (*(const float32*)((const void*)(&localval))) as a load from a float32 type and concludes these aren't to the same location so localval can be in a register containing something while it loads from an uninitialized location on the stack reserved for localval should it decide it needs to "spill" that register back to its reserved "automatic" storage (stack). It may or may not store localval to memory before dereferencing the pointer and loading from memory. Depending on what follows in your code it may decide that localval isn't used and the assignment of something has no side-effect, so it may decide that assignment is "dead code" and not even do the assignment to a register.

语言标准对严格遵循的程序的语义(行为)非常具体——编译器编写器或语言实现者的责任是使其正确。一旦程序员越界并调用未定义行为的标准是明确的举证责任,这将按预期工作的程序员,而不是在编译器作者——在这种情况下,编译器已经好足够的警告说,未定义的行为已经被调用,虽然它甚至没有义务这样做。有时令人恼火的是,人们会告诉你,在这一点上“任何事情都有可能发生”,之后通常会有一些笑话或夸张。在您的程序中,编译器可以生成“平台的典型代码”,并将某些内容的值存储到localval中,然后像您希望的那样从localval加载并存储到DataPtr中,但是要知道它没有义务这么做。它把商店localval uint32存储东西的类型和它把废弃的负载(*(const float32 *)((const void *)(&localval)))作为负载从float32类型和总结这些不是相同的位置所以localval可以在寄存器包含未初始化的位置虽然它加载的东西在堆栈上留给localval应该决定它需要登记“泄漏”回到了保留“自动”存储(栈)。在取消引用指针并从内存加载之前,它可以或不可以将localval存储到内存中。根据代码中的内容,它可能会决定不使用localval,并且赋值没有副作用,因此它可能会决定赋值是“死代码”,甚至不会对寄存器执行赋值。

2) As both would occupy the same size (correct me if not) what could be the side affects of such a compiler optimization ?

2)由于两者都占用相同的大小(如果没有的话,请纠正我)这样的编译器优化的副作用是什么?

The effect could be that an undefined value is stored at the address pointed to by DataPtr.

结果可能是一个未定义的值存储在DataPtr指定的地址。

3) Can I safely ignore the warning or turn off aliasing ?

3)我能安全忽略警告或关闭别名吗?

That is specific to the compiler you are using - if the compiler documents a way to turn off the strict aliasing optimizations then yes, with whatever caveats the compiler makes.

这是特定于您正在使用的编译器的—如果编译器记录了一种关闭严格的别名优化的方法,那么是的,无论编译器发出什么警告。

4) If the compiler hasn't performed an optimization and my program is not broken after my first compilation ? Can i safely assume that every time the compiler would behave the same way (does not do optimizations) ?

4)如果编译器没有执行优化,我的程序在第一次编译后没有被破坏?我是否可以安全地假设每次编译器的行为都是相同的(不做优化)?

Maybe, sometimes very small changes in another part of your program could change what the compiler does to this code, think for a moment if the function is "inlined" it could be thrown in the mix of some other part of your code, see this SO question.

也许,有时候你的程序的另一部分中很小的改变就能改变编译器对这段代码所做的事情,想想如果这个函数是“内联的”它可能会被扔进你代码的其他部分中,看看这个问题。

5) Does the aliasing apply to a void * typecast too ? or is it applicable only for the standard typecasts (int,float etc...) ?

5)别名是否也适用于void * typecast ?还是只适用于标准类型(int,float等)?

You cannot dereference a void * so the compiler just cares about the type of your final cast (and in C++ it would gripe if you convert a const to non-const and vice-versa).

您不能取消对void *的引用,因此编译器只关心您的最终强制类型(在c++中,如果您将const转换为non-const,反之亦然)。

6) what are the affects if I disable the aliasing rules ?

6)如果我禁用混叠规则会有什么影响?

See your compiler's documentation - in general you will get slower code, if you do this (like the Linux kernel chose to do in the example from the paper above) then limit this to a small compilation unit, with only the functions where this is necessary.

请参阅编译器的文档——一般来说,如果您这样做的话,您将会得到更慢的代码(就像上面文章中的Linux内核所选择的那样),然后将其限制为一个小的编译单元,只有必要的函数。

Conclusion

结论

I understand your questions are for curiosity and trying to better understand how this works (or might not work). You mentioned it is a requirement that the code be portable, by implication then it is a requirement that the program be compliant and not invoke undefined behavior (remember, the burden is on you if you do). In this case, as you pointed out in the question, one solution is to use memcpy, as it turns out not only does that make your code compliant and therefore portable, it also does what you intend in the most efficient way possible on current gcc with optimization level -O3 the compiler converts the memcpy into a single instruction storing the value of localval at the address pointed to by DataPtr, see it live in coliru here - look for the movl %esi, (%rdi) instruction.

我理解你的问题是出于好奇,并试图更好地理解它是如何工作的(或者可能不工作)。您提到过,要求代码是可移植的,这意味着,要求程序是兼容的,而不是调用未定义的行为(请记住,如果您这么做的话,负担就在您身上)。在这种情况下,当你指出的问题,一个解决方案是使用memcpy,结果是不仅使代码兼容的,因此便携,它也会按照你的意愿移动以最有效的方式可能与优化级别o3对当前gcc编译器将memcpy转换成一个单指令存储localval的价值指向的地址DataPtr,看到这里住在coliru——寻找movl % esi,(% rdi)指令。

#2


3  

You have an incomplete example (as written, it exhibits UB since localval is uninitialized) so let me complete it:

您有一个不完整的示例(正如所写的,由于localval未初始化,它显示了UB),所以让我来完成它:

uint32 localval;
void * DataPtr;
DataPtr = something;
localval = 42;
(*(float32*)(DataPtr))= (*(const float32*)((const void*)(&localval)));

Now, since localval has type uint32 and *(const float32*)((const void*)(&localval)) has type float32, they cannot alias, so the compiler is free to reorder the last two statements with respect to each other. This would obviously result in behavior different from what you want.

现在,由于localval有类型uint32和*(const float32*)(const void*)(&localval))有类型float32,它们不能别名,因此编译器可以*地对最后两个语句进行重排序。这显然会导致不同于你想要的行为。

The correct way to write this is:

正确的写法是:

memcpy(DataPtr, &localval, sizeof localval);

#3


2  

The const makes no difference. To check if the types are the same size, you can compare sizeof (uint32) to sizeof (float32). It's also possible that the two types have differing alignment requirements.

这没什么区别。要检查类型是否相同,可以比较sizeof (uint32)和sizeof (float32)。这两种类型也可能有不同的对齐需求。

Those things aside; the behaviour is undefined to read the memory of localval as if it had a float stored in it, that's what the strict aliasing rules say.

这些事情放在一边;这种行为没有定义为读取localval的内存,就好像其中存储了一个浮点数一样,这就是严格的别名规则所说的。

6.5#6:

6.5 # 6:

The effective type of an object for an access to its stored value is the declared type of the object, if any.

访问其存储值的对象的有效类型是对象的声明类型(如果有的话)。

6.5#7:

6.5 # 7:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types

对象只能通过具有以下类型之一的lvalue表达式访问其存储值

localval has effective type uint32 , and the list of "the following types" doesn't include float32 so this is a violation of the aliasing rules.

localval具有有效类型uint32,并且“以下类型”的列表不包括float32,因此这违反了混叠规则。

If you were aliasing in dynamically allocated memory, then it is different. There's no "declared type", so the "effective type" is whatever was last stored in the object. You could malloc(sizeof (uint32)), and then store a float32 in it and read it back.

如果你在动态分配的内存中混叠,那么它是不同的。没有“已声明的类型”,所以“有效类型”是最后存储在对象中的内容。您可以malloc(sizeof (uint32)),然后在其中存储一个float32并读取它。

To sum up, you seem to be asking "I know this is undefined, but can I rely on my compiler successfully doing it?" To answer that question you will have to specify what your compiler is, and what switches you are invoking it with, at least.

总而言之,您似乎在问:“我知道这是未定义的,但是我可以依赖我的编译器成功地完成它吗?”要回答这个问题,您必须指定编译器是什么,以及您要调用它的什么开关。

Of course there is also the option of adjusting your code so it does not violate the strict-aliasing rules, but you haven't provided enough background info to proceed down this track.

当然,也有调整代码的选项,这样它就不会违反严格的混叠规则,但是您还没有提供足够的背景信息来进行这个跟踪。