2008-10-26 55 views
14

我曾经在嵌入式系统的项目中工作过,在那里我们重新安排了堆栈变量的声明顺序,以减小生成的可执行文件的大小。举例来说,如果我们有:C编译器能否重新安排堆栈变量?

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

我们将重新排序这是:

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

由于对准问题的第一个导致12个字节的堆栈空间被使用,第二个导致只有8个字节。

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

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

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

编辑

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

再次编辑

谢谢大家的帮助。对于文档,我能找到的最好的东西是文件Optimal Stack Slot Assignment in GCC(pdf),由Naveen Sharma和Sanjiv Kumar Gupta在2003年GCC峰会会议上提交。

这里的问题项目是使用ADS编译器进行ARM开发。在该编译器的文档中提到,由于ARM-Thumb体系结构如何计算本地堆栈帧中的地址,因此像我所示的排序声明可以提高性能以及堆栈大小。该编译器不会自动重新安排当地人利用这一点。这里链接的文章说,从2003年起,GCC也没有重新安排堆栈框架来改善ARM-Thumb处理器的参考位置,但这意味着您可以。

我无法找到任何明确表示这已在GCC中实施的任何内容,但我认为这篇论文被认为是证明你们都是正确的。再次感谢。

回答

22

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

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

IIRC较新的MSVC编译器在与当地人的缓冲区溢出作斗争时使用这种自由度。

作为一个提示,在C++中,即使编译器重新对内存布局进行了重新排序,销毁顺序也必须与声明的反向顺序相同。

(我不能提出旁证,虽然,这是从内存。)

0

它是编译器的细节,可以让他自己的编译器,如果他想这样做会反过来。

10

编译器是免费的,甚至从堆栈中删除该变量,使其只注册如果分析结果显示的地址该变量从不被使用/使用。

+0

这是一个很好的观点。当我和我的同事讨论这件事时,我并没有这样做。 – 2008-10-26 19:27:52

+1

它甚至可以将多个变量分配到相同的寄存器或堆栈位置,如果它可以证明变量在相同的代码段中从不存在。这是很常见的做法,特别是内联代码会导致短暂的可变生命。 – 2008-10-26 21:05:51

4

编译器甚至可能根本没有使用数据堆栈。如果你的平台非常小,以至于你担心8到12个字节的堆栈,那么很可能会有编译器具有非常特殊的方法。 (想起一些PIC和8051编译器)

你在编译什么处理器?

+0

这是我刚刚工作的一个项目,我们正在使用旧版本的Arm Developer Suite(ADS)编译器为几个ARM处理器构建。我真的只是要求解决关于其他编译器如何处理这个问题的讨论。 – 2008-10-26 19:41:29

0

体面的编译器会将局部变量放入寄存器中(如果可以的话)。如果寄存器压力过大(空间不足)或者变量的地址已被占用,这意味着它需要存在内存中,变量只能放在堆栈上。

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

+0

当地人几乎总是必须在某个时候倾倒到堆栈。唯一的例外是如果你的函数*从不*调用另一个函数,在这种情况下,一切都是暂时的,并且可以在没有堆栈帧的情况下处理。 – 2008-10-26 19:53:53

10

堆栈甚至不需要存在(实际上,C99标准没有单个“堆栈”单词)。所以是的,编译器可以自由地执行任何想要的操作,只要它保留具有自动存储持续时间的变量的语义。

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

0

AFAIK C或C++的定义中没有指定编译器应如何在堆栈中排序局部变量。我会说,依靠编译器在这种情况下可能做的是一个坏主意,因为下一个版本的编译器可能会有所不同。如果您花费时间和精力来订购本地变量以节省堆栈的几个字节,那么对于系统的功能而言,这些少量字节最好是非常关键的。

4

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

这是你的代码被重新安排的地方,而不仅仅是当地人。所以执行顺序最终不会达到你所期望的。

C和C++不要实际上承诺一个内存模型(就JVM而言),所以事情可能会非常不同,并且仍然合法。

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

PIC和兔子嵌入式板不堆栈,除非你问得特别好。

0

没有必要对C标准要求或不要求的空闲猜测:最近的草稿可从ANSI/ISO working group在线免费获得。

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... 
} 
38

不仅可以编译器重新安排局部变量的堆栈布局,它可以将它们分配到寄存器,将其分配到寄存器有时住和有时在堆栈中,它可以将两个本地人分配到内存中的同一个插槽(如果他们的生存区域不重叠),甚至可以完全消除变量。