㈠ 不同的编译器生成的二进制文件不同吗
是 一样 的
㈡ 编译器生成的汇编语句执行顺序为什么与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
㈢ linux编译器的解析顺序与windows编译器的区别
没区别,都是编译成obj lib等目标文件和库文件,然后链接为可执行的二进制代码,Win平台多了一个动态链接库。
㈣ flash 两次编译成swf 二进制源码为什么不同
flash反编译后,经过修改还能再次编译成 swf文件。
反编译一般用闪客精灵
在更改的过程中,代码不出错误,才能正常编译成功。
在更改过程中,仅仅是修改了文字或者是图片,一般是能成功编译的。
㈤ 二进制代码和二进制数据有什么不同
楼主混淆了表义和表现两者
也就是说,代码和数据的表现都是二进制编码,但是真正的含义(即用途)是不同的
代码和数据的编码都是二进制(计算机中)的,数字本身没有什么区别
但是如果一段二进制编码,从意义层面上来讲具备执行能力,那么就认为是代码
而如果此二进制编码,作为数据被读取并处理,那么就是数据
举个简单的例子就好理解了:
同样的可执行文件exe:如果交给操作系统的程序加载器,它会解析其中的text段,以执行计算机指令,这部分的二进制编码从意义上讲就是你说的二进制代码
但是如果exe被病毒读取,以达到感染的目的,那么病毒会读取text段,并有选择的修改段代码,这时这部分二进制编码对病毒来说就是二进制数据了
㈥ 为什么说,用c语言写的程序比汇编语言写的程序执行效率上要低,难道两者编译成的二进制代码有很大不同
汇编可以直接操作硬件,而C语言则对这些底层操作进行了一定程度的封装,而封装就意味着执行过程复杂度的增加。所以说,虽然二者最后实现的效果是一样的,但后者的执行过程要比前者复杂,编译成的二进制代码也就有所不同。
㈦ 自已编译源码和别人编译好的二进制源码有多大区别
没感觉出来它们两个用上去有什么不同。
基本上用现成的二进制就行,我见过太多自己编译的其实就是出于心理作用。
㈧ 求教大神,经常听说java是解释性语言,和c这样的编译性语言不同,有什么不同,最后还不都是二进制代码
没错,JAVA是一种解释性语言。也就是说,编译处理后的JAVA程序并不是由能被CPU直接识别运行的机器指令构成,而是一种二进制的通用伪代码。
要运行这种由伪代码构成的程序,必须要有JAVA虚拟机JVM。
运行时,从程序中取一条伪指令,在JVM中翻译成一串机器指令,运行。再取一条指令,再翻译,再运行……
使用JVM的缺点,是它的程序运行效率低,速度慢。
使用JVM的优点,就是不同的计算机硬件和不同的操作系统,只要配上对应的JVM,都可以运行同一个JAVA程序。因而JAVA程序的通用性好,用JAVA开发软件的人工效率较高。
JAVA一般用来开发那些对通用性要求高而对运行效率要求不很苛刻的应用程序。
C语言和C++等编译语言的程序,是直接由CPU运行的机器指令,运行时不再需要二次翻译,所以运行时能最大限度地发挥CPU的效能,运行速度很快。但在不同的硬件或操作系统平台上,程序需要按平台重新编译,甚至修改或重写代码。它的程序缺乏跨平台的通用性,开发软件的人力投入较大。
对运行速度有很高要求的软件
,如操作系统,大型游戏的核心引擎,和大多数有较高的运行速度效率要求的应用软件,多用C++这类编译性语言开发。
㈨ 同一个文件GCC历次编译出来的二进制文件都一样的吗
除非你自己写一个编译器,要不然是不可能链接出不带elf的二进制文件。只要你在linux下就只能得到elf格式的.
㈩ VB中的顺序文件、随机文件和二进制文件的区别
顺序、随机、二进制是指一个程序打开文件所用的方式
可以顺序访问/随机访问,比如标准输入输出等特殊文件就只能顺序访问
还可以用文本模式/二进制模式打开,主要是对换行符的处理有所不同