16

过去我从事过嵌入式系统的项目,我们重新安排了堆栈变量的声明顺序以减小生成的可执行文件的大小。例如,如果我们有:

void func()
{
    char c;
    int i;
    short s;
    ...
}

我们将其重新排序为:

void func()
{
    int i;
    short s;
    char c;
    ...
}

由于对齐问题,第一个导致使用了 12 个字节的堆栈空间,而第二个导致仅使用了 8 个字节。

这是 C 编译器的标准行为还是我们使用的编译器的一个缺点?

在我看来,如果编译器愿意,它应该能够重新排序堆栈变量以支持更小的可执行文件大小。有人向我建议 C 标准的某些方面阻止了这种情况,但无论哪种方式我都无法找到有信誉的来源。

作为一个额外的问题,这是否也适用于 C++ 编译器?

编辑

如果答案是肯定的,C/C++ 编译器可以重新排列堆栈变量,你能举一个绝对做到这一点的编译器的例子吗?我想查看编译器文档或类似的东西来支持这一点。

再次编辑

谢谢大家的帮助。对于文档,我能找到的最好的东西是 Naveen Sharma 和 Sanjiv Kumar Gupta 的论文GCC 中的 Optimal Stack Slot Assignment (pdf),它在 2003 年的 GCC 峰会上发表。

这里讨论的项目是使用 ADS 编译器进行 ARM 开发。在该编译器的文档中提到,像我展示的排序声明可以提高性能以及堆栈大小,因为 ARM-Thumb 体系结构如何计算本地堆栈帧中的地址。该编译器不会自动重新排列本地变量以利用这一点。此处链接的论文说,截至 2003 年,GCC 也没有重新排列堆栈帧以改善 ARM-Thumb 处理器的参考位置,但这意味着您可以。

我找不到任何可以肯定地说这曾经在 GCC 中实现过的东西,但我认为这篇论文可以证明你都是正确的。再次感谢。

4

11 回答 11

40

编译器不仅可以重新排序局部变量的堆栈布局,还可以将它们分配给寄存器,将它们分配给有时在寄存器中有时在堆栈中,它可以将两个局部变量分配给内存中的同一个槽(如果它们的生存范围不要重叠),它甚至可以完全消除变量。

于 2008-11-04T06:30:45.690 回答
24

由于标准中没有任何内容禁止 C 或 C++ 编译器这样做,是的,编译器可以做到这一点。

对于必须保持相对顺序的聚合(即结构)则不同,但编译器仍然可以插入填充字节以实现更好的对齐。

IIRC 较新的 MSVC 编译器使用这种自由来对抗本地缓冲区溢出。

附带说明一下,在 C++ 中,即使编译器重新排序内存布局,销毁的顺序也必须与声明的相反顺序。

(我不能引用章节和诗句,但这是凭记忆。)

于 2008-10-26T19:04:28.237 回答
10

编译器甚至可以自由地从堆栈中删除变量并使其注册,前提是分析表明该变量的地址从未被占用/使用。

于 2008-10-26T19:22:40.057 回答
10

堆栈甚至不需要存在(事实上,C99 标准中没有出现一次“堆栈”一词)。所以是的,编译器可以自由地做它想做的任何事情,只要它保留具有自动存储持续时间的变量的语义。

举个例子:我多次遇到这样的情况,因为它存储在寄存器中,所以无法在调试器中显示局部变量。

于 2008-10-26T19:38:38.963 回答
5

德州仪器 62xx 系列 DSP 的编译器能够进行“整个程序优化”。(你可以关掉它)

这是您的代码重新排列的地方,而不仅仅是本地代码。所以执行顺序最终并不完全符合您的预期。

C 和 C++实际上并没有承诺内存模型(在 JVM 的意义上),所以事情可能完全不同并且仍然合法。

对于那些不了解它们的人,62xx 系列是每个时钟周期 8 条指令的 DSP;在 750Mhz 时,它们确实在 6e+9 指令处达到峰值。反正有些时候。它们进行并行执行,但指令排序是在编译器中完成的,而不是 CPU,就像 Intel x86 一样。

PIC 和 Rabbit 嵌入式板没有堆栈,除非你特别好问

于 2008-10-26T21:03:47.357 回答
4

编译器甚至可能根本不使用堆栈来存储数据。如果您在一个非常小的平台上,以至于您担心 8 字节和 12 字节的堆栈,那么很可能会有编译器具有非常专业的方法。(想到一些 PIC 和 8051 编译器)

你要为什么处理器编译?

于 2008-10-26T19:28:19.873 回答
0

这是编译器的细节,如果他想要那样的话,可以制作自己的编译器来做相反的事情。

于 2008-10-26T18:58:19.030 回答
0

如果可以的话,一个体面的编译器会将局部变量放在寄存器中。只有当寄存器压力过大(没有足够的空间)或者变量的地址被占用时,变量才应该放在堆栈上,这意味着它需要存在于内存中。

据我所知,没有什么说变量需要放置在 C/C++ 堆栈上的任何特定位置或对齐位置;编译器会将它们放在最适合性能和/或编译器编写者方便的地方。

于 2008-10-26T19:30:21.493 回答
0

AFAIK 在 C 或 C++ 的定义中没有任何内容指定编译器应如何在堆栈上对局部变量进行排序。我会说在这种情况下依赖编译器可能会做的事情是个坏主意,因为下一个版本的编译器可能会做不同的事情。如果您花费时间和精力对局部变量进行排序以节省几个字节的堆栈,那么这几个字节最好对您的系统运行非常关键。

于 2008-10-26T19:43:38.143 回答
0

无需对 C 标准需要或不需要什么进行无谓的猜测:最近的草案可从ANSI/ISO 工作组在线免费获得。

于 2008-10-26T21:10:13.973 回答
0

这不能回答您的问题,但这是我关于相关问题的 2 美分...

我没有堆栈空间优化的问题,但我遇到了堆栈上双变量未对齐的问题。可以从任何其他函数调用函数,并且堆栈指针值可以具有任何未对齐的值。所以我想出了下面的想法。这不是原始代码,我只是写了它......

#pragma pack(push, 16)

typedef struct _S_speedy_struct{

 double fval[4];
 int64  lval[4];
 int32  ival[8];

}S_speedy_struct;

#pragma pack(pop)

int function(...)
{
  int i, t, rv;
  S_speedy_struct *ptr;
  char buff[112]; // sizeof(struct) + alignment

  // ugly , I know , but it works...
  t = (int)buff;
  t +=  15; // alignment - 1
  t &= -16; // alignment
  ptr = (S_speedy_struct *)t;

  // speedy code goes on...
}
于 2008-10-28T15:28:42.227 回答