问题描述
我编写了一个简单的程序来了解这一点:
#include <stdio.h>
int main(void)
{
int i = 3,*x;
char j = 'c',*y;
int k = 1,*z;
x = &i;
y = &j;
z = &k;
fprintf(stdout,"Address in x = %p\n",x);
fprintf(stdout,"Address in y = %p\n",y);
fprintf(stdout,"Address in z = %p\n",z);
fprintf(stdout,"Subtracting x from z = %li\n",z - x);
return 0;
}
我多次运行了该程序,并且总是得到本质上相似的输出:
Address in x = 0x7ffd7e981348
Address in y = 0x7ffd7e981347
Address in z = 0x7ffd7e98134c
Subtracting x from z = 1
...尽管,源代码在两个char j
之间定义了ints i and k
;对于我的系统编译器组合,始终为i
和k
分配了连续的内存位置,即分别4 bytes
或1 * sizeof(int)
。我假设编译器按type
分组,并为其类型分配连续的内存位置。但是,像这样...更改源代码并向混合添加float
...
int main(void)
{
int i = 3,*w;
char j = 'c',*x;
float k = 3.145,*y;
int l = 1,*z;
w = &i;
x = &j;
y = &k;
z = &l;
fprintf(stdout,"Address of i = %p\n",w);
fprintf(stdout,"Address of j = %p\n","Address of k = %p\n","Address of l = %p\n","Subtracting w from z = %li\n",z - w);
return 0;
}
输出...
Address of i = 0x7ffe256f04cc
Address of j = 0x7ffe256f04cb
Address of k = 0x7ffe256f04d0
Address of l = 0x7ffe256f04d4
Subtracting w from z = 2
...等等,编译器不是按type
分组,而是按size
分组。所以,我的问题是
- 所有编译器是否总是将具有相同大小的变量组合在一起,然后在堆栈上分配它们的连续内存位置?
- 这是否也会发生在其他存储段中的所有存储类和变量/常量中?
- 为什么要这样做?这是某种优化吗?如果是,它如何“优化”东西?
TIA!
解决方法
您可以看到反汇编代码。那可以帮助您解决问题。
首先,函数分配给空白空间并分配值大小 如果创建3个值(x,y,z)。所以,像这样反汇编。
// create new 3 values
int x = 0
int y = 1
int z = 2
// To assembly
PUSH EBP
MOV ESP,EBP
SUB ESP,0x0C
MOV [EBP-4],0
MOV [EBP-8],1
MOV [EBP-C],2
它向您展示,为什么内存分配连续的空间。
然后优化每个编译器都不同。就像有时它删除const值,并且制定了不同的调用约定。
,在分配(自动)变量到堆栈时,编译器是否按大小分组并分配它们的连续位置?
阅读C11标准n1570。
允许编译器将automatic variables在call stack上的位置重新排序,或者重新排列optimize使其仅保留在processor registers中。
足够聪明的编译器可能会在编译时计算w = &i;
(但实际上要注意ASLR)。其他编译器可能会做一些inline expansion。阅读Dragon book和PLDI会议或ACM SIGPLAN接受的最新论文。
也尝试cross-compiling的Arduino的C代码。您可能会感到惊讶。
IIRC,GCC的某些较早版本进行了优化遍历(在足够简单的情况下)对自动变量进行了重新排序。但是请注意Rice's theorem。
如果您使用最新的GCC(在2020年10月,这意味着GCC 10),请尝试使用example.c
来编译gcc -O3 -fverbose-asm -S -Wall -Wextra example.c
代码,然后查看发出的example.s
汇编代码
阅读this draft报告(由CHARIOT和DECODER H2020项目资助),并研究MILEPOST GCC和CTuning项目
还尝试在C代码上使用Frama-C和Clang tidy static analyzer。阅读documentation of GCC,特别是有关Invoking GCC的章节和有关Static Analyzer Options的章节。
您可以考虑研究source code的开源C编译器,例如GCC,Clang,tinycc或{{3} }。您还可以查看nwcc项目的内部。
您可以考虑编写自己的CompCert来重组局部变量(预算超过一年的工作时间)。然后,请写一篇关于它的会议论文。您可以从这样的工作中获得博士学位。
如果可以,它究竟如何“优化”东西?
详细了解GCC plugin技术和abstract interpretation应用于Robinson arithmetic的技术。许多编译器都使用其中的一些,您的GCC插件也可能会使用它们(也许通过将其耦合到某些whole program optimization上,例如2021年的proof assistant)。