【问题标题】:gcc stack overflown due to weird optimization由于奇怪的优化,gcc 堆栈溢出
【发布时间】:2020-12-03 11:27:10
【问题描述】:

我设置了一些类似于下面示例的代码。这是针对内存有限的嵌入式 ARM 系统,所以我的堆栈对于这个线程只有 800 字节。

基本思想是我从模块获取故障编号,值为 0 会清除该模块的所有故障,唯一编号可能会触发特定故障。 我布置功能的方式的主要目标是只写一次唯一编号,并且与故障本身在同一行。 目前我正在处理可以通过这种方式触发的 91 个故障。

enum {
    MOD_1, // 0
    MOD_2,
    MOD_3,
    MOD_4,
    MOD_5,
    MOD_6,
    MOD_7,
    // end
    MOD_LAST,
};

extern short mod_fault[7];

struct fault_mess {
    short MOD_1_SOME_FAULT;
    short MOD_1_DIFFERENT_FAULT;
    ... //more mod 1 faults
    short MOD_1_FINAL_FAULT;

    ... //other mod faults

    short MOD_7_SOME_FAULT;
    short MOD_7_DIFFERENT_FAULT;
    ... //more mod 7 faults
    short MOD_7_FINAL_FAULT;    
}
struct fault_mess fault;

void set_mod_fault(void)
{
    short x, tmp, tmp2;

    for (x=0; x < MOD_LAST; x++)      // check all modules
    {
        if (x == MOD_1)
        {
            tmp = mod_fault[x];
            tmp2 = !!tmp;
            if(!tmp2 | tmp == 67) fault.MOD_1_SOME_FAULT = tmp2;
            if(!tmp2 | tmp == 44) fault.MOD_1_DIFFERENT_FAULT = tmp2;
            ...
            if(!tmp2 | tmp == 69) fault.MOD_1_FINAL_FAULT = tmp2;
        }
        
        ... //more else if cases
        
        else if (x == MOD_7)
        {
            tmp = mod_fault[x];
            tmp2 = !!tmp;
            if(!tmp2 | tmp == 52) fault.MOD_7_SOME_FAULT = tmp2;
            if(!tmp2 | tmp == 81) fault.MOD_7_DIFFERENT_FAULT = tmp2;
            ...
            if(!tmp2 | tmp == 17) fault.MOD_7_FINAL_FAULT = tmp2;
        }
    }
}

我遇到的问题是 gcc 生成此代码指令的方式。 (这是为 32 位 ARM,v4T 编译的,使用 -O2)

我已经逐步完成了生成的汇编代码,它正在预先计算每个if(!tmp2 | tmp == 52) 的结果并将值推入堆栈。然后它进入循环并根据堆栈中的值有条件地存储tmp2 的值。堆栈上的值是 4 字节宽,相当于用于此“优化”的 364 字节堆栈。如果我在 -O0 下编译,则没有过多的堆栈使用。

所以代码在技术上是正确的,因为如果我将堆栈增加到足够大,它将运行而不会崩溃。但是编译器的行为似乎不直观,而且有些错误。

我可以通过重构代码来解决这个问题,但我很好奇这个问题是否有据可查,或者其他人是否认为这是应该解决的问题。

编辑:我尝试使用“-fstack-usage”进行编译,它生成了一个 *.su 文件。 该文件表明该函数在-O0下编译时需要16字节的堆栈,而在-O2下编译时实际上需要960字节的堆栈。

这是一个网络示例。它的性能不如我的嵌入式编译器,使用 -O2 仅使用 200 个字节,(与使用 -O0 的 16 个字节相比)http://tpcg.io/w3KBdVZ6

【问题讨论】:

  • 好的,第一件事。表达式!tmp2 | tmp == 52 使用了不正确的运算符(| 而不是||)或存在优先级问题。
  • 第二 - 这需要minimal reproducible exampleextern short mod_fault[7]; 实际定义在哪里?第三 - 问题最有可能在您的代码中,而不是在优化中。
  • 我真的怀疑编译器会将中间结果存储在堆栈上。
  • 如果代码看起来像是使用了大量的复制/粘贴来创建它,那么它可能需要清理。
  • @EugeneSh.:但我认为在这种特殊情况下,||| 具有相同的效果。

标签: c gcc arm compiler-optimization


【解决方案1】:

它预先计算每个 if(!tmp2 | tmp == 52) 的结果并将值压入堆栈。

在您编辑问题以使用选项 -S 向我们展示生成的汇编程序之前,我对此表示严重怀疑

...总计 364 字节的堆栈 如果我将堆栈增加到足够大,它将运行而不会崩溃

即使您所说的推送是否正确,是否有 364 个字节也不会爆炸您的堆栈,原因在其他地方,您很可能在某处有未定义的行为

代码在技术上是正确的

很抱歉,你的代码真的很乱!

struct fault_mess 似乎有 91 个字段 (7*('M' - 'A' + 1)) 是没有意义的,你为什么不使用例如 char fault[MOD_LAST]['M' - 'A' + 1] 允许进行第一级简化:

for (x=0; x < MOD_LAST; x++)      // check all modules
{
  if (!mod_fault[x])
  {
    memset(fault[x], 0, sizeof(fault[x]));
  }
  else
  {
    tmp = mod_fault[x];

    switch (x) {
    case MOD_1:
      switch (tmp) {
      case 67: fault[MOD_1]['A'-'A'] = 1; break;
      case 44: fault[MOD_1]['B'-'A'] = 1; break;
      ...
      }
      break;
    ...
    case MOD_7:
      switch (tmp) {
      case 52: fault[MOD_7]['A'-'A'] = 1; break;
      case 81: fault[MOD_7]['B'-'A'] = 1; break;
      ...
      }
      break;
    }
  }
}

我使用char 的数组来表示故障,但您保存bool(0 或1)

第二级简化可以定义为char 的常量二维数组,其第一维是MOD_LAST,第二个13 ('M' - 'A' + 1) 与

arr[MOD_1]['A'-'A'] = 67
arr[MOD_1]['B'-'A'] = 44
...
arr[MOD_7]['A'-'A'] = 52
arr[MOD_7]['B'-'A'] = 81
..

只需要短代码:

for (x=0; x < MOD_LAST; x++)      // check all modules
{
  if (!mod_fault[x])
  {
    memset(fault[x], 0, sizeof(fault[x]));
  }
  else
  {
    short tmp = mod_fault[x];
    short * p = arr[x];

    for (i = 0; i != 'M' - 'A' + 1; ++i)
    {
       if (tmp == p[i])
       {
          fault[x][i] = 1;
          break;
       }
    }
  }
}

请注意,如果mod_fault 的可能值范围很小,您还可以使用char [MOD_LAST][max-range] 的常量数组为0 .. 'M' - 'A' + 1 赋值或任何超出范围的值来检测这种情况,然后删除for 循环,然后

#define MIN_VALUE ??
#define MAX_VALUE ??
#define NOT_INDEX 'Z' 
...
for (x=0; x < MOD_LAST; x++)      // check all modules
{
  if (!mod_fault[x])
  {
    memset(fault[x], 0, sizeof(fault[x]));
  }
  else if ((mod_fault[x] >= MIN_VALUE) && (mod_fault[x] <= MAX_VALUE))
  {
    char index = arr[x][mod_fault[x] - MIN_VALUE];
   
    if (index != NOT_INDEX)
      fault[x][index] = 1;
  }
}

arr[MOD_1][76 - MIN_VALUE] = 0; // A
arr[MOD_1][44 - MIN_VALUE] = 1; // B
...
arr[MOD_7][52 - MIN_VALUE] = 0; // A
arr[MOD_7][81 - MIN_VALUE] = 1; // B
...

以及其他重视NOT_INDEX的条目

【讨论】:

  • “struct fault_mess”是一个比我在这里指出的大得多的结构,它与系统中的许多其他地方相关联。系统对这些故障作出反应并以专有格式记录它们。本质上我是说我不能改变结构的格式。每个故障唯一重要的是它的确切名称。
  • 我在一个内存有限的嵌入式系统上。堆栈正好有 800 个字节。我正在做一些澄清,希望我可以发布一个可重复的示例。
  • 你的代码对于这样一个有限的系统来说是相当浪费的。
  • @ZacharyVanderKlippe 您使用复杂的代码丢失了很多内存,并且当您提供的代码中只有一位足以在您提供的代码中保存 0 或 1 时,还使用 ​​short 字段 MOD_&lt;n&gt;_SOME_FAULT_&lt;X&gt; 跨度>
  • 我认为你非常大的struct fault_mess 结构显示了设计问题。
猜你喜欢
  • 2012-02-14
  • 2013-09-04
  • 2011-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-18
相关资源
最近更新 更多