首页 > 其他分享 >C编译获取预编译中间文件

C编译获取预编译中间文件

时间:2023-07-12 14:11:28浏览次数:42  
标签:__ 文件 executable code 获取 编译 program file print

 

方法一:

Journey of a C Program to Linux Executable in 4 Stages (thegeekstuff.com)

C源码到可执行文件的preprocess/compile/assemble/link四阶段_zh_yt的博客-CSDN博客

 

使用 -save-temps 选项时

打开根目录下的CMakeList文件中加上

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -save-temps=obj")
set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -save-temps=obj")


$ gcc --help

-save-temps              Do not delete intermediate files
-E                       Preprocess only; do not compile, assemble or link
-S                       Compile only; do not assemble or link
-c                       Compile and assemble, but do not link
-o <file>                Place the output into <file>

 

 

方法二:

      针对CMake+Makefile模式,打开build目录下的Makefile文件,在Help Target那里可以看到有xxx.i 预处理的目标文件,可以使用make xxx.i 来生成

 

 

 

 

Journey of a C Program to Linux Executable in 4 Stages

by HIMANSHU ARORA on OCTOBER 5, 2011

You write a C program, use gcc to compile it, and you get an executable. It is pretty simple. Right?

Have you ever wondered what happens during the compilation process and how the C program gets converted to an executable?

There are four main stages through which a source code passes in order to finally become an executable.

The four stages for a C program to become an executable are the following:

  1. Pre-processing
  2. Compilation
  3. Assembly
  4. Linking

In Part-I of this article series, we will discuss the steps that the gcc compiler goes through when a C program source code is compiled into an executable.

Before going any further, lets take a quick look on how to compile and run a ‘C’ code using gcc, using a simple hello world example.

$ vi print.c
#include <stdio.h>
#define STRING "Hello World"
int main(void)
{
/* Using a macro to print 'Hello World'*/
printf(STRING);
return 0;
}

Now, lets run gcc compiler over this source code to create the executable.

$ gcc -Wall print.c -o print

In the above command:

 
  • gcc – Invokes the GNU C compiler
  • -Wall – gcc flag that enables all warnings. -W stands for warning, and we are passing “all” to -W.
  • print.c – Input C program
  • -o print – Instruct C compiler to create the C executable as print. If you don’t specify -o, by default C compiler will create the executable with name a.out

Finally, execute print which will execute the C program and display hello world.

$ ./print
Hello World

Note: When you are working on a big project that contains several C program, use make utility to manage your C program compilation as we discussed earlier.

Now that we have a basic idea about how gcc is used to convert a source code into binary, we’ll review the 4 stages a C program has to go through to become an executable.

1. PRE-PROCESSING

This is the very first stage through which a source code passes. In this stage the following tasks are done:

  1. Macro substitution
  2. Comments are stripped off
  3. Expansion of the included files

To understand preprocessing better, you can compile the above ‘print.c’ program using flag -E, which will print the preprocessed output to stdout.

$ gcc -Wall -E print.c

Even better, you can use flag ‘-save-temps’ as shown below. ‘-save-temps’ flag instructs compiler to store the temporary intermediate files used by the gcc compiler in the current directory.

$ gcc -Wall -save-temps print.c -o print

So when we compile the program print.c with -save-temps flag we get the following intermediate files in the current directory (along with the print executable)

$ ls
print.i
print.s
print.o

The preprocessed output is stored in the temporary file that has the extension .i (i.e ‘print.i’ in this example)

Now, lets open print.i file and view the content.

$ vi print.i
......
......
......
......
# 846 "/usr/include/stdio.h" 3 4
extern FILE *popen (__const char *__command, __const char *__modes) ;
extern int pclose (FILE *__stream);
extern char *ctermid (char *__s) __attribute__ ((__nothrow__));

# 886 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__));

# 916 "/usr/include/stdio.h" 3 4
# 2 "print.c" 2

int main(void)
{
printf("Hello World");
return 0;
}

In the above output, you can see that the source file is now filled with lots and lots of information, but still at the end of it we can see the lines of code written by us. Lets analyze on these lines of code first.

  1. The first observation is that the argument to printf() now contains directly the string “Hello World” rather than the macro. In fact the macro definition and usage has completely disappeared. This proves the first task that all the macros are expanded in the preprocessing stage.
  2. The second observation is that the comment that we wrote in our original code is not there. This proves that all the comments are stripped off.
  3. The third observation is that beside the line ‘#include’ is missing and instead of that we see whole lot of code in its place. So its safe to conclude that stdio.h has been expanded and literally included in our source file. Hence we understand how the compiler is able to see the declaration of printf() function.

When I searched print.i file, I found, The function printf is declared as:

extern int printf (__const char *__restrict __format, ...);

The keyword ‘extern’ tells that the function printf() is not defined here. It is external to this file. We will later see how gcc gets to the definition of printf().

You can use gdb to debug your c programs. Now that we have a decent understanding on what happens during the preprocessing stage. let us move on to the next stage.

2. COMPILING

After the compiler is done with the pre-processor stage. The next step is to take print.i as input, compile it and produce an intermediate compiled output. The output file for this stage is ‘print.s’. The output present in print.s is assembly level instructions.

Open the print.s file in an editor and view the content.

$ vi print.s
.file "print.c"
.section .rodata
.LC0:
.string "Hello World"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
movq %rsp, %rbp
.cfi_offset 6, -16
.cfi_def_cfa_register 6
movl $.LC0, %eax
movq %rax, %rdi
movl $0, %eax
call printf
movl $0, %eax
leave
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits

Though I am not much into assembly level programming but a quick look concludes that this assembly level output is in some form of instructions which the assembler can understand and convert it into machine level language.

3. ASSEMBLY

At this stage the print.s file is taken as an input and an intermediate file print.o is produced. This file is also known as the object file.

This file is produced by the assembler that understands and converts a ‘.s’ file with assembly instructions into a ‘.o’ object file which contains machine level instructions. At this stage only the existing code is converted into machine language, the function calls like printf() are not resolved.

Since the output of this stage is a machine level file (print.o). So we cannot view the content of it. If you still try to open the print.o and view it, you’ll see something that is totally not readable.

$ vi print.o
^?ELF^B^A^A^@^@^@^@^@^@^@^@^@^A^@>^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@0^
^@UH<89>å¸^@^@^@^@H<89>ǸHello World^@^@GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3^@^
T^@^@^@^@^@^@^@^AzR^@^Ax^P^A^[^L^G^H<90>^A^@^@^\^@^@]^@^@^@^@A^N^PC<86>^B^M^F
^@^@^@^@^@^@^@^@.symtab^@.strtab^@.shstrtab^@.rela.text^@.data^@.bss^@.rodata
^@.comment^@.note.GNU-stack^@.rela.eh_frame^@^@^@^@^@^@^@^@^@^@^@^
...
...
…

The only thing we can explain by looking at the print.o file is about the string ELF.

ELF stands for executable and linkable format.

This is a relatively new format for machine level object files and executable that are produced by gcc. Prior to this, a format known as a.out was used. ELF is said to be more sophisticated format than a.out (We might dig deeper into the ELF format in some other future article).

Note: If you compile your code without specifying the name of the output file, the output file produced has name ‘a.out’ but the format now have changed to ELF. It is just that the default executable file name remains the same.

4. LINKING

This is the final stage at which all the linking of function calls with their definitions are done. As discussed earlier, till this stage gcc doesn’t know about the definition of functions like printf(). Until the compiler knows exactly where all of these functions are implemented, it simply uses a place-holder for the function call. It is at this stage, the definition of printf() is resolved and the actual address of the function printf() is plugged in.

The linker comes into action at this stage and does this task.

The linker also does some extra work; it combines some extra code to our program that is required when the program starts and when the program ends. For example, there is code which is standard for setting up the running environment like passing command line arguments, passing environment variables to every program. Similarly some standard code that is required to return the return value of the program to the system.

The above tasks of the compiler can be verified by a small experiment. Since now we already know that the linker converts .o file (print.o) to an executable file (print).

So if we compare the file sizes of both the print.o and print file, we’ll see the difference.

$ size print.o
   text	   data	    bss	    dec	    hex	filename
     97	      0	      0	     97	     61	print.o 

$ size print
   text	   data	    bss	    dec	    hex	filename
   1181	    520	     16	   1717	    6b5	print

Through the size command we get a rough idea about how the size of the output file increases from an object file to an executable file. This is all because of that extra standard code that linker combines with our program.

Now you know what happens to a C program before it becomes an executable. You know about Preprocessing, Compiling, Assembly, and Linking stages There is lot more to the linking stage, which we will cover in our next article in this series.

 

 

 

标签:__,文件,executable,code,获取,编译,program,file,print
From: https://www.cnblogs.com/sinferwu/p/17547331.html

相关文章

  • linux下批量重命名目录及子目录下的文件
    一、加上后缀名假如只是给当前目录及所有子目录下的文件添加后缀名,使用find和mv就可以了。比如把当前及子目录下所有带_test后缀的文件加上.c后缀find.-typef-name'*_test'-execmv{}{}.c\;find.查找当前及子目录,GNU版本的find也可以省略点号,效果一样。......
  • Java实现浏览器端大文件分片上传技术
    ​ javaweb上传文件上传文件的jsp中的部分上传文件同样可以使用form表单向后端发请求,也可以使用ajax向后端发请求    1.通过form表单向后端发送请求         <formid="postForm"action="${pageContext.request.contextPath}/UploadServlet"method="post"e......
  • C源码到可执行文件的preprocess/compile/assemble/link四阶段
     C源码到可执行文件的preprocess/compile/assemble/link四阶段_zh_yt的博客-CSDN博客   C源码到可执行文件的preprocess/compile/assemble/link四阶段参考资料http://www.thegeekstuff.com/2011/10/c-program-to-an-executable/http://courses.cms.caltech.edu/cs11/ma......
  • 盘点前端实现文件下载的几种方式
    前端涉及到的文件下载还是很多应用场景的,那么前端文件下载有多少种方式呢?每种方式有什么优缺点呢?下面就来一一介绍。1.使用a标签下载通过a标签的download属性来实现文件下载,这种方式是最简单的,也是我们比较常用的方式,先来看示例代码:<a href="http://www.baidu.com" downl......
  • FTP文件传输协议
    简述FTP:文件传输协议,FileTransferProtocol,是在互联网中进行文件传输的一种协议,基于C/S模式,客户端通过FTP协议与服务器建立连接,并进行文件的上传、下载和管理。在Linux系统下,有一款工具实现ftp协议,名为vsftpd,非常安全的FTP守护进程服务默认端口控制端口:默认端口号是21。该端......
  • Java实现浏览器端大文件分片上传实例解析
    ​ 我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。首先我们需要了解的是上传文件三要素:1.表单提交方式:post(get方式提交有大小限制,post没有)2.表单的enctype属性:必须设置为multipart/form-data.3.表单必须......
  • Linux | 如何复制文件夹到另一个文件夹
    linux系统使用cp命令即可将一个文件夹里面的文件复制到另外一个文件夹里面。这个命令相当于dos下面的copy命令,具体用法是:cp-r源文件目的文件,其中参数r是指连同源文件中的子目录一同拷贝。在linux系统中复制文件我们可以使用cp或copy命令了,我们要复制文件或目录都可以使用它们......
  • C# 选择文件选择设置类型示例
     例子:OpenFileDialogdialog=newOpenFileDialog();dialog.Multiselect=false;//该值确定是否可以选择多个文件dialog.Title="请选择文件";dialog.Filter="图像文件(*.jpg;*.png;*.bmp)|*.jpg;*.png;*.bmp;*.jpg......
  • win10文件异地备份
    1、依次打开控制面板—管理工具—任务计划程序。2、依次点击展开任务计划程序库—Microsoft—Windows—WindowsBackup。  3、在右侧窗口空白处点击右键,在弹出的菜单中点击新建任务。 4、根据提示输入计划的名称和描述。 5、点击触发器选项卡,在下方点击新建,在弹出的窗......
  • Prettier 配置指南 .prettierrc.json 文件配置指南
    1.在项目根目录下面,新建.prettierrc.json文件2.prettuer配置{"printWidth":100,"tabWidth":2,"useTabs":true,"semi":true,"singleQuote":true,"trailingComma":"none","b......