为什么 GCC 11 编译器在启用优化时会产生奇怪的输出?
Why is GCC 11 compiler producing weird output when optimization is enabled?
请看一下这段代码:
#include <stdio.h>
#include <stdint.h>
int main()
{
uint8_t run = 1;
/* Define variable of interest and set to random value */
uint32_t dwTime = 0xdeadc0de;
/* Define uint16_t pointer */
uint16_t* tmpU16;
/* Assign least 16 bits from dwTime to tmpU16 by downcasting from uint32_t to uint16_t */
tmpU16 = (uint16_t*)&dwTime;
/* Print content of tmpU16 */
fprintf(stderr, "TEST: %04x\n", *tmpU16); /* Will print "TEST: c0de" here */
/* This loop will run exactly once */
while (run)
{
/* Print content of tmpU16 AGAIN (content of tmpU16 should not have changed here!) */
/* Will print "TEST: 0000" here when optimization is enabled */
/* Will print "TEST: c0de" here when optimization is disabled */
fprintf(stderr, "TEST: %04x\n", *tmpU16);
/* Increment tmpU16 pointer by 1 (yes, this does not make sense but it should not do any harm
either, unless something gets written to its address AFTER incrementing */
tmpU16++;
run--;
}
return 0;
}
使用 GCC 11 编译此代码并启用优化(例如 -O2
)时,将产生以下输出:
TEST: c0de
TEST: 0000
当此代码未经优化编译时 (-O0
) 它将产生:
TEST: c0de
TEST: c0de
我假设在第一个fprintf
执行后,while循环内的下一个fprintf
将立即执行。在这两个 fprintf
之间不应发生任何事情,因此 tmpU16
的内容保持不变。
但是,当我注释掉 tmpU16++
指针增量时,它会通过优化生成正确的输出。
这是为什么,这是怎么回事?
这一行:
tmpU16 = (uint16_t*)&dwTime;
您将一种类型的对象视为另一种不兼容的类型,这违反了严格的别名规定。
C standard 的第 6.5p7 节列出了可能出现混叠的情况:
An object shall have its stored value accessed only by an lvalue
expression that has one of the following types:88)
- a type compatible with the effective type of the object,
- a qualified version of a type compatible with the effective type of the object,
- a type that is the signed or unsigned type corresponding to the effective type of the object,
- a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
- an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a
subaggregate or contained union), or
- a character type.
88 ) The intent of this list is to specify those circumstances in which an object may or may not be aliased
使用指向 uint16_t
的指针访问 uint32_t
不属于这些类别。这样的违规触发 undefined behavior.
特别是对于 gcc,除非您使用 -O2
或更高版本,或者明确使用 -fstrict-aliasing
,否则不会启用严格别名规则的实施。通过强制执行此规则,编译器能够进行其他方式无法进行的优化。
请看一下这段代码:
#include <stdio.h>
#include <stdint.h>
int main()
{
uint8_t run = 1;
/* Define variable of interest and set to random value */
uint32_t dwTime = 0xdeadc0de;
/* Define uint16_t pointer */
uint16_t* tmpU16;
/* Assign least 16 bits from dwTime to tmpU16 by downcasting from uint32_t to uint16_t */
tmpU16 = (uint16_t*)&dwTime;
/* Print content of tmpU16 */
fprintf(stderr, "TEST: %04x\n", *tmpU16); /* Will print "TEST: c0de" here */
/* This loop will run exactly once */
while (run)
{
/* Print content of tmpU16 AGAIN (content of tmpU16 should not have changed here!) */
/* Will print "TEST: 0000" here when optimization is enabled */
/* Will print "TEST: c0de" here when optimization is disabled */
fprintf(stderr, "TEST: %04x\n", *tmpU16);
/* Increment tmpU16 pointer by 1 (yes, this does not make sense but it should not do any harm
either, unless something gets written to its address AFTER incrementing */
tmpU16++;
run--;
}
return 0;
}
使用 GCC 11 编译此代码并启用优化(例如 -O2
)时,将产生以下输出:
TEST: c0de
TEST: 0000
当此代码未经优化编译时 (-O0
) 它将产生:
TEST: c0de
TEST: c0de
我假设在第一个fprintf
执行后,while循环内的下一个fprintf
将立即执行。在这两个 fprintf
之间不应发生任何事情,因此 tmpU16
的内容保持不变。
但是,当我注释掉 tmpU16++
指针增量时,它会通过优化生成正确的输出。
这是为什么,这是怎么回事?
这一行:
tmpU16 = (uint16_t*)&dwTime;
您将一种类型的对象视为另一种不兼容的类型,这违反了严格的别名规定。
C standard 的第 6.5p7 节列出了可能出现混叠的情况:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:88)
- a type compatible with the effective type of the object,
- a qualified version of a type compatible with the effective type of the object,
- a type that is the signed or unsigned type corresponding to the effective type of the object,
- a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
- an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
- a character type.
88 ) The intent of this list is to specify those circumstances in which an object may or may not be aliased
使用指向 uint16_t
的指针访问 uint32_t
不属于这些类别。这样的违规触发 undefined behavior.
特别是对于 gcc,除非您使用 -O2
或更高版本,或者明确使用 -fstrict-aliasing
,否则不会启用严格别名规则的实施。通过强制执行此规则,编译器能够进行其他方式无法进行的优化。