你不断声称使用C编写的程序可以在其他人的计算机上无问题地使用。但这只有在他们也运行Windows而不是Linux、OS X、OpenBSD或Solaris等其他操作系统时才成立。你必须使用不同的C库来显示窗口或进行网络I/O(“ping google.com”)。特别是“ping”,因为发送ICMP echo-request数据包通常是一个特权操作,所以非常不可移植。(例如,在Linux上,“ping”可执行文件是setuid-root的,因此它可以强制执行速率限制。)你可能会使用"system("ping google.com")"而不是在你自己的程序中使用网络套接字来完成它。更好的例子是进行HTTP请求,因为任何进程都可以打开TCP连接。
你可以编写可移植的C程序,但你肯定需要努力使它们可移植。人们需要源代码,以便他们可以为自己的系统编译它。(这就是为什么Unix有一个传统,即以源代码形式分发软件:每个人都需要使用自己版本的库为自己的系统编译它。)
编译后的二进制文件仅能在其编译目标平台上运行,例如x86-64 Windows。这样的二进制文件无法在ARM Windows或仅支持32位的x86 Windows上运行。
当使用汇编语言编写代码时,你的源代码是针对特定平台(包括CPU,而不仅仅是可用的系统调用和库)的。因此,你将为32位x86 Windows编写代码,而不仅仅是“为Windows”编写代码,使C编译器能够从同一源代码生成x86 32位或x86-64 64位二进制文件。
如果你只关心32位x86 Windows,那么这并没有太大的区别。(每台“普通”的Windows计算机都可以运行32位x86二进制文件,因此如果想要在其他Windows计算机上实现可移植性,应该制作32位x86二进制文件。)
例如,让我们使用
Godbolt编译器探索器(源代码+汇编)来查看一个微不足道的C程序在两个不同的x86平台上如何编译为不同的汇编代码:
#include <stdio.h>
int main() { puts("Hello World"); }
使用gcc -O3
编译(Intel语法模式而非AT&T,Godbolt将其传递给-masm=intel
),在x86-64 Linux(x86-64 System V调用约定)上将编译为此汇编代码:
.LC0:
.string "Hello World"
main:
sub rsp, 8 # align the stack before a call
mov edi, OFFSET FLAT:.LC0
call puts # first arg passed in RDI
xor eax, eax # eax = 0 = return value.
add rsp, 8 # restore the stack
ret
但是MSVC针对32位x86 Windows将其编译为以下汇编代码:
$SG5328 DB 'Hello World', 00H
EXTRN _puts:PROC
_main PROC
push OFFSET $SG5328
call _puts
add esp, 4
xor eax, eax
ret 0
_main ENDP
汇编语法不同(MASM vs. GAS),但这不是重要的事情。重要的是,字符串字面值的指针通过堆栈(使用push)传递,而不是寄存器。
在Windows上,puts的汇编符号名称以_为前缀,但在Linux上没有。
由于这是32位代码,堆栈槽的宽度仅为4个字节,而不是8个字节。
这适用于ISO C函数(如puts),可在两个平台上使用。在Windows上,您可以在WinAPI DLL中调用MessageBoxA,但Linux没有“本地”图形API。大多数桌面需要X11库,但并非所有人都使用X11。
既然您已经了解C语言,查看编译器输出是学习汇编语言的好方法。请参阅Matt Godbolt在CppCon2017的演讲{{link1:“最近我的编译器为我做了什么?揭开编译器的盖子”}}。(此外,有关编写编译为有趣汇编代码的小函数的更多建议:如何从GCC / clang汇编输出中删除“噪音”?)。