Skip to Content

gcc编译时文件扩展名为.S和.s的区别

gcc编译时,文件扩展名为.S和.s的区别是,.S支持预处理,而.s不支持。

gcc编译一般分为四个阶段,分别是预处理、编译、汇编、链接。

下面我们用一个小例子看下这四个阶段的作用,示例代码:

$ ls
lib.c  lib.h  main.c

$ cat lib.h
#ifndef __LIB_H
#define __LIB_H

extern int add(int a, int b);

#endif /* __LIB_H */

$ cat lib.c
#include "lib.h"

int add(int a, int b) {
  return a + b;
}

$ cat main.c
#include "lib.h"

#define A 1
#define B 2

int main() {
  return add(A, B);
}

预处理的作用是宏展开和头文件替换:

$ gcc -E main.c -o main.i
$ cat main.i
// 删除一些无关内容
extern int add(int a, int b);

int main() {
  return add(1, 2);
}

编译的作用是把c代码转成汇编代码:

$ gcc -fno-asynchronous-unwind-tables -S main.i
$ ls
lib.c  lib.h  main.c  main.i  main.s
$ cat main.s
	.file	"main.c"
	.text
	.globl	main
	.type	main, @function
main:
	pushq	%rbp
	movq	%rsp, %rbp
	movl	$2, %esi
	movl	$1, %edi
	call	add@PLT
	popq	%rbp
	ret
	.size	main, .-main
	.ident	"GCC: (GNU) 9.2.0"
	.section	.note.GNU-stack,"",@progbits

汇编的作用是将汇编代码转成对应的二进制形式的cpu指令:

$ gcc -c main.s
$ ls
lib.c  lib.h  main.c  main.i  main.o  main.s
$ file main.o
main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

链接的作用是把代码之间的引用关系关联起来,最终生成一个完整的程序:

$ gcc -c lib.c
$ ls
lib.c  lib.h  lib.o  main.c  main.i  main.o  main.s
$ gcc main.o lib.o
$ ls
a.out  lib.c  lib.h  lib.o  main.c  main.i  main.o  main.s
$ ./a.out; echo $?
3

由上可见,文件扩展名为.s的文件其实就是汇编代码文件。

其实我们可以直接编写汇编代码,保存到以.s为后缀的文件里,然后再用gcc将其编译成可执行文件。

但.s为后缀的文件不支持预处理,如果我们想在汇编代码里使用宏或头文件,则保存该汇编代码的文件必须以.S结尾。

写个例子看下:

$ cat hello.s
#define MSG "hello"
	.global main
	.text
main:
	mov	$message, %rdi
	call	puts
	ret
message:
	.asciz MSG

$ gcc -no-pie hello.s
hello.s: Assembler messages:
hello.s:10: Error: junk at end of line, first unrecognized character is `M'

$ mv hello.s hello.S
$ gcc -no-pie hello.S
$ ./a.out
hello

由上可见,当文件扩展名为.s时,宏MSG是无法识别的,但扩展名改为.S后,该汇编代码可正常编译并执行。

希望对你有所帮助。

Categories: gcc
Tags: c