① 如何让编译器按代码顺序执行(((急急急)))
问题是你do的值就是c.me提供的,想要优化那就要考虑把do(c.me())这整个逻辑进行优化
② 编译器生成的汇编语句执行顺序为什么与C代码顺序不同
不影响语义的前提下编译器可以任意重排代码顺序;
在乱序执行(Out-of-Order)的CPU里,机器码的执行也可以不按照你在“汇编”层面上看到的顺序执行,只要不影响语义。
所以说这些中间步骤的顺序,作为底层细节平时不需要那么在意——它们多半跟原始源码的顺序是不一样的。
现代优化编译器优化的思路之一是“基于依赖的优化”(dependence-based optimization)。题主引用的CSAPP的例子:
int arith(int x, int y, int z) {
int t1 = x + y;
int t2 = z * 48;
int t3 = t1 & 0xFFFF;
int t4 = t2 * t3;
return t4;
}
所有涉及运算的值都是局部标量变量(local scalar variable),这是最便于编译器做分析的情况,所有依赖都可以显式分析。
由于整个函数没有分支,这里也不需要讨论控制依赖(control dependence),只要讨论数据依赖(data dependence)就好。
把数据依赖图画出来是个DAG(这里正好是棵树,特例了):
x y z 48
\ / \ /
t1 0xFFFF t2
\ / /
t3 /
\ /
t4
优化必须要满足的约束是:每个节点求值之前,其子节点(依赖的数据源)必须要先求了值。
显然,t1和t2之间没有依赖关系,它们的相对求值顺序怎样重排都没关系。
有本我很喜欢的书,里面讲的是各种基于依赖的优化:Optimizing Compilers for Modern Architectures - A Dependence-based Approach
以上是理论部分。
================================================================
下面来看例子。
我们可以用一个实际编译器来看看CSAPP的例子编译出来的结果:
.text
# -- Begin arith
.p2align 4,,15
.globl arith
.type arith, @function
arith:
.p2align 4,,7
/*.L0:*/ /* Block BB[54:2] preds: none, freq: 1.000 */
movl 8(%esp), %edx /* ia32_Load T[139:10] -:1:22 */
addl 4(%esp), %edx /* ia32_Add Iu[141:12] -:2:14 */
movzwl %dx, %edx /* ia32_Conv_I2I Iu[142:13] -:4:15 */
imull 12(%esp), %edx /* ia32_IMul Iu[143:14] -:5:15 */
leal (%edx,%edx,2), %eax /* ia32_Lea Iu[144:15] -:5:15 */
shll $0x4, %eax /* ia32_Shl Iu[146:17] -:5:15 */
ret /* ia32_Return X[152:23] -:6:3 */
.size arith, .-arith
# -- End arith
这里用的是libFirm。可见它跟CSAPP书里所说的汇编的顺序又有所不同。这也是完全合理的。
这个编译结果的顺序是:
edx = y;
edx += x;
edx = zeroextend dx; // edx = edx & 0xFFFF
edx *= z;
eax = edx * 3;
eax <<= 4; // eax = eax * 16
也是完全符合依赖关系的约束的一种顺序。
之所以用libFirm举例是因为它的中间表示(Intermediate Representation)是一种程序依赖图(Program Dependence Graph),可以很方便的看出控制与数据依赖。把CSAPP那里例子对应的libFirm IR画出来,是这个样子的:
(这张图跟我前面画的数据依赖图正好是左右翻转的,不过意思一样。(这张图跟我前面画的数据依赖图正好是左右翻转的,不过意思一样。
Arg 0、1、2分别代表x、y、z。白色方块是普通数据节点,黄色方块是常量节点,蓝色方块是内存相关节点,红色方块是控制流节点,粉红色方块是特殊的开始/结束节点。)
某版LLVM生成的代码:
; MoleID = '/tmp/webcompile/_16355_0.bc'
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-ellcc-linux"
; Function Attrs: nounwind readnone
define i32 @arith(i32 %x, i32 %y, i32 %z) #0 {
entry:
%add = add nsw i32 %y, %x
%mul = mul nsw i32 %z, 48
%and = and i32 %add, 65535
%mul1 = mul nsw i32 %mul, %and
ret i32 %mul1
}
attributes #0 = { nounwind readnone "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.ident = !{!0}
!0 = !{!"ecc 0.1.10 based on clang version 3.7.0 (trunk) (based on LLVM 3.7.0svn)"}
最终生成的x86汇编:
.text
.file "/tmp/webcompile/_15964_0.c"
.globl arith
.align 16, 0x90
.type arith,@function
arith: # @arith
# BB#0: # %entry
movl 8(%esp), %eax
addl 4(%esp), %eax
movzwl %ax, %eax
imull 12(%esp), %eax
shll $4, %eax
leal (%eax,%eax,2), %eax
retl
.Ltmp0:
.size arith, .Ltmp0-arith
.ident "ecc 0.1.10 based on clang version 3.7.0 (trunk) (based on LLVM 3.7.0svn)"
.section ".note.GNU-stack","",@progbits
GCC 4.9.2 x86-64:
arith(int, int, int):
leal (%rdx,%rdx,2), %eax
addl %edi, %esi
movzwl %si, %esi
sall $4, %eax
imull %esi, %eax
ret
Zing VM Server Compiler x86-64:
# edi: x
# esi: y
# edx: z
movl %edx, %eax
shll $0x4, %eax
leal (%rsi, %rdi, 1), %ecx
shll $0x5, %edx
addl %edx, $eax
movzwl %ecx, %edx
imull %edx, %eax
③ 编译器编译代码时,他的顺序是怎样的
编译是一个很复杂的东西 不是一两句能说清楚的
你可以搜一下编译原理,这个可以说是一门学科了
大多数C编译器遵循的一个流程
预编译(宏取代 条件编译处理等)
词法分析 根据关键字获取符号
语法分析 分析程序结构 大多数的编译错误都是这一步抛出来的
中间代码转换
转为二级制码
④ 关于译码器器的描述正确的是
尽可能通俗讲
假设我们有一篇英文说明书。
编译器负责把每一页或节或章翻译成等价的中文。
链接器负责把翻译好的章节整理成完整说明书。
由于语言问题,译本和原本之间的页数可能不一样,导致原本中位于第二页的某图在译本里可能在第五页,这些引用的修改由链接器完成。又译本中可能还要加上中文的版本历史,序以及译者的话等,甚至出于一些考虑还要改变原本的章节顺序。
上述所有这些活都是链接器的基本工作。
⑤ 如何arm编译器编译时变量顺序排列
可以 话说,目前业内一般都是用keil编译器的,它支持的芯片种类还更多
⑥ c++编译器以何种顺序编译文件的,先cpp文件,还是.h文件
1. 编译阶段 (头文件 .h)
d工程中在头文件中对导出内容(function, class, type, object, variable)进行定义.
2. 链接阶段 (库文件 .lib)
d工程在link阶段会生成.lib
用户link时需要 这个.lib 解决link时的代码定位.
3. 运行阶段
.exe
⑦ 编译器生成的汇编语句执行顺序为什么与C代码顺序不同
编译器不仅是无序的,而且还展开、合并、删除代码(如果没有发现代码),,,C语言是好的,和C++编译器编译,经常连上帝也不知道结果。
暂停现代CPU为了弥补CPU和内存之间的速度差距的接入方案,以所谓的缓存(L1,L2,L3;在CPU芯片的速度比内存快得多,但容量很小),CPU缓存中常见的事情似乎保持镜子,需要在缓存中直接读取使用的数据是好的,比内存快几倍的速度。
注意名字的区别。名称风险是两个没有数据流的指令,数据冒险是两个指令之间的数据流。
⑧ 编译器编译代码时, 他的顺序是怎样的
先定义全局变量,再按照从左至右,从上至下的顺序将源代码(也就是你写的代码)编译成机器能识别的机器码,最后再执行编译好的机器码.
⑨ c/c++中,编译器编译文件的顺序和规则
#include <filename>从磁盘安装根目录查找文件;#include "filename"从编译文件的当前目录查找文件。
⑩ linux编译器的解析顺序与windows编译器的区别
没区别,都是编译成obj lib等目标文件和库文件,然后链接为可执行的二进制代码,Win平台多了一个动态链接库。