C编译器错误或程序错误?
C compiler bug or program error?
我正在使用 IAR C 编译器为嵌入式微处理器(特别是 Renesas uPD78F0537)构建应用程序。在此应用程序中,我使用两个嵌套的 for 循环来初始化一些数据,如下所示 MCVE:
#include <stdio.h>
#define NUM_OF_OBJS 254
#define MAX_OBJ_SIZE 4
unsigned char objs[NUM_OF_OBJS][MAX_OBJ_SIZE];
unsigned char srcData[NUM_OF_OBJS][MAX_OBJ_SIZE];
void main(void)
{
srcData[161][2] = 10;
int x, y;
for (x = 0; x < NUM_OF_OBJS; x++)
{
for (y = 0; y < MAX_OBJ_SIZE; y++)
{
objs[x][y] = srcData[x][y];
}
}
printf("%d\n", (int) objs[161][2]);
}
输出值为0,不是10。
编译器正在为 for 循环生成以下代码:
13 int x, y;
14 for (x = 0; x < NUM_OF_OBJS; x++)
\ 0006 14.... MOVW DE,#objs
\ 0009 16.... MOVW HL,#srcData
15 {
16 for (y = 0; y < MAX_OBJ_SIZE; y++)
\ 000C A0F8 MOV X,#248
17 {
18 objs[x][y] = srcData[x][y];
\ ??main_0:
\ 000E 87 MOV A,[HL]
\ 000F 95 MOV [DE],A
19 }
\ 0010 86 INCW HL
\ 0011 84 INCW DE
\ 0012 50 DEC X
\ 0013 BDF9 BNZ ??main_0
20 }
以上不起作用:编译器显然是在预先计算 NUM_OF_OBJS x MAX_OBJ_SIZE = 1016 (0x3f8)。该值用作计数器,但它被截断为 8 位 (0xf8 == 248) 并存储在 8 位寄存器 'X' 中。因此,只有前 248 个字节的数据被初始化,而不是完整的 1016 个字节。
我可以解决这个问题,但我的问题是:这是编译器错误吗?还是我忽略了什么?
更新
- 这个微控制器有 7KB 的 RAM,并且 sizeof(int) == 2
- 使用指针复制数据(例如
while (len-- > 0) *dst++ = *src++;
中的内容)效果很好。
鉴于这是完整的代码,编译器没有义务做任何事情,因为代码是无稽之谈。编译器可以自由优化整个循环,因为它什么都不做。
完全符合标准的编译器必须将 objs
和 srcData
都初始化为全零,因为它们具有静态存储持续时间。
因此,嵌套循环只是将零从一个数组移到另一个数组。如果编译器注意到这个循环没有意义,它可以完全删除循环。
当然,减少循环中的迭代次数作为一种优化方式并没有多大意义,因此人们可能想知道优化器做出了哪些奇怪的决定来得出该机器代码。虽然看起来很奇怪和愚蠢,但它完全符合标准。
您可以将循环迭代器声明为 volatile
以强制执行副作用。在那种情况下,循环必须执行,即使它毫无意义,因为 reading/writing 到 volatile
变量是副作用,不允许编译器优化。
请记住,嵌入式系统编译器通常有一个非标准的 "minimal start-up" 选项,它们跳过静态存储持续时间变量的初始化以实现更快的系统启动。如果启用此类非标准选项,变量将包含垃圾值而不是零。
虽然从技术上讲,人们可能会假设这些数组位于 .bss 中并已归零,但当您实际做出该假设并在写入之前从 bss 中读取某些内容时,编译器现在会发出警告。作为全局中断等可能会进入并修改这些数组,因此(编译器)它们是否为零真的是一个安全的假设。 Gcc 没有做出这个假设,它复制了整个数组。 Clang/llvm 也是复制整个数组,不走捷径。一种快捷方式看起来像是将一个值写入两个数组并打印该值,而不是复制整个数组。
有趣的是,即使数组被声明为静态或本地化,gcc 也无法找到快捷方式,这对 gcc 来说很奇怪,它不是最好的,但通常会找出更复杂的死代码循环。
为此编译器目标定义的 int 的大小是多少,可能 int 的大小是 8 位并且编译器执行了您要求的操作。如果不是,那么也许是,这是一个编译器错误。尝试让 254 变小一点,它是否会按比例改变 0xF8,它是否总是看起来好像正在切掉低 8 位,或者这是一个神奇的数字,0xF8 与 1016 或 254 或 161 有某种关系,等等
我非常确信这是一个基于以下内容的编译器错误:
使用指针复制数据(例如 while (len-- > 0) *dst++ = *src++;
中的内容)效果很好。因此这看起来不像是 RAM 大小、指针大小等问题
也许更相关:如果我只是用静态变量替换两个常量(NUM_OF_OBJS
或 MAX_OBJ_SIZE
)之一(从而防止编译器预先计算总数) 它工作正常。
不幸的是,我联系了 IAR(为这个 SO 问题提供了 link),这是他们的回答:
Sorry to read that you don't have a license/support-agreement (SUA).
An examination such as needed for this case can take time, and we
(post-sales-support) prioritize to put time and effort to users with
valid SUA.
以及一些不是特别有用的通用注释。
所以我想我会假设这是一个错误并在我的代码中解决它(有很多可能的解决方法,包括上面描述的两个)。
我正在使用 IAR C 编译器为嵌入式微处理器(特别是 Renesas uPD78F0537)构建应用程序。在此应用程序中,我使用两个嵌套的 for 循环来初始化一些数据,如下所示 MCVE:
#include <stdio.h>
#define NUM_OF_OBJS 254
#define MAX_OBJ_SIZE 4
unsigned char objs[NUM_OF_OBJS][MAX_OBJ_SIZE];
unsigned char srcData[NUM_OF_OBJS][MAX_OBJ_SIZE];
void main(void)
{
srcData[161][2] = 10;
int x, y;
for (x = 0; x < NUM_OF_OBJS; x++)
{
for (y = 0; y < MAX_OBJ_SIZE; y++)
{
objs[x][y] = srcData[x][y];
}
}
printf("%d\n", (int) objs[161][2]);
}
输出值为0,不是10。
编译器正在为 for 循环生成以下代码:
13 int x, y;
14 for (x = 0; x < NUM_OF_OBJS; x++)
\ 0006 14.... MOVW DE,#objs
\ 0009 16.... MOVW HL,#srcData
15 {
16 for (y = 0; y < MAX_OBJ_SIZE; y++)
\ 000C A0F8 MOV X,#248
17 {
18 objs[x][y] = srcData[x][y];
\ ??main_0:
\ 000E 87 MOV A,[HL]
\ 000F 95 MOV [DE],A
19 }
\ 0010 86 INCW HL
\ 0011 84 INCW DE
\ 0012 50 DEC X
\ 0013 BDF9 BNZ ??main_0
20 }
以上不起作用:编译器显然是在预先计算 NUM_OF_OBJS x MAX_OBJ_SIZE = 1016 (0x3f8)。该值用作计数器,但它被截断为 8 位 (0xf8 == 248) 并存储在 8 位寄存器 'X' 中。因此,只有前 248 个字节的数据被初始化,而不是完整的 1016 个字节。
我可以解决这个问题,但我的问题是:这是编译器错误吗?还是我忽略了什么?
更新
- 这个微控制器有 7KB 的 RAM,并且 sizeof(int) == 2
- 使用指针复制数据(例如
while (len-- > 0) *dst++ = *src++;
中的内容)效果很好。
鉴于这是完整的代码,编译器没有义务做任何事情,因为代码是无稽之谈。编译器可以自由优化整个循环,因为它什么都不做。
完全符合标准的编译器必须将 objs
和 srcData
都初始化为全零,因为它们具有静态存储持续时间。
因此,嵌套循环只是将零从一个数组移到另一个数组。如果编译器注意到这个循环没有意义,它可以完全删除循环。
当然,减少循环中的迭代次数作为一种优化方式并没有多大意义,因此人们可能想知道优化器做出了哪些奇怪的决定来得出该机器代码。虽然看起来很奇怪和愚蠢,但它完全符合标准。
您可以将循环迭代器声明为 volatile
以强制执行副作用。在那种情况下,循环必须执行,即使它毫无意义,因为 reading/writing 到 volatile
变量是副作用,不允许编译器优化。
请记住,嵌入式系统编译器通常有一个非标准的 "minimal start-up" 选项,它们跳过静态存储持续时间变量的初始化以实现更快的系统启动。如果启用此类非标准选项,变量将包含垃圾值而不是零。
虽然从技术上讲,人们可能会假设这些数组位于 .bss 中并已归零,但当您实际做出该假设并在写入之前从 bss 中读取某些内容时,编译器现在会发出警告。作为全局中断等可能会进入并修改这些数组,因此(编译器)它们是否为零真的是一个安全的假设。 Gcc 没有做出这个假设,它复制了整个数组。 Clang/llvm 也是复制整个数组,不走捷径。一种快捷方式看起来像是将一个值写入两个数组并打印该值,而不是复制整个数组。
有趣的是,即使数组被声明为静态或本地化,gcc 也无法找到快捷方式,这对 gcc 来说很奇怪,它不是最好的,但通常会找出更复杂的死代码循环。
为此编译器目标定义的 int 的大小是多少,可能 int 的大小是 8 位并且编译器执行了您要求的操作。如果不是,那么也许是,这是一个编译器错误。尝试让 254 变小一点,它是否会按比例改变 0xF8,它是否总是看起来好像正在切掉低 8 位,或者这是一个神奇的数字,0xF8 与 1016 或 254 或 161 有某种关系,等等
我非常确信这是一个基于以下内容的编译器错误:
使用指针复制数据(例如
while (len-- > 0) *dst++ = *src++;
中的内容)效果很好。因此这看起来不像是 RAM 大小、指针大小等问题也许更相关:如果我只是用静态变量替换两个常量(
NUM_OF_OBJS
或MAX_OBJ_SIZE
)之一(从而防止编译器预先计算总数) 它工作正常。
不幸的是,我联系了 IAR(为这个 SO 问题提供了 link),这是他们的回答:
Sorry to read that you don't have a license/support-agreement (SUA). An examination such as needed for this case can take time, and we (post-sales-support) prioritize to put time and effort to users with valid SUA.
以及一些不是特别有用的通用注释。
所以我想我会假设这是一个错误并在我的代码中解决它(有很多可能的解决方法,包括上面描述的两个)。