c++源码如何变成程序
2018-11-01 22:52:40 weixin_41429999 阅读数 1549

相信很多人同我一样,在刚刚接触C语言的时候,只是找了一本教材,或者是找了一套教学视频,跟着慢慢学习C语言的语法,并没有去多想一个.c文件在后台究竟是经过了怎样的步骤才最终变成.exe文件;就在前几天,本人闲着无聊翻开了在书架上吃灰将近一年的“全新”CSAPP,在看到其第一章的内容之后,恍然大悟,姑且水一篇博客纪念一下。

 


首先我们来简要看一下CSAPP原书上的内容:(这是我按照自己的理解结合原书内容敲下的,也许会存在事实上的错误,尊重原书的说法。)

一个.c源码最终变成可执行文件要经过以下步骤:

  • 预处理阶段:预处理器根据以#开头的指令,修改源码内容,比如如果源码中有 #include<stdio.h> 则预处理器会读取文件 stdio.h 文件中的内容,并将其直接插入到原来的源码文件中,通常另存为以 .i 为扩展名的文件。
  • 编译阶段:编译器读取 .i 文件中的内容,并将其翻译为以 .s 为扩展名的汇编语言文件。
  • 汇编阶段:汇编器将 .s 文件翻译成机器码,并保存为 .o为扩展名的文件。
  • 链接阶段:链接器将不同的 .o 文件合并到一起,组成最终的可执行文件;比如我们的程序里调用了 printf 函数,作为一个C标准函数,printf 单独存在于一个 printf.o 的文件中,那么链接器将会找到这个 printf.o 文件,将其中的内容合并到我们自己的 .o 文件中,生成可以被加载到内存中执行的文件。

 光看理论没啥意思,让我们来手动操作一下:

首先新建一个 hello.c 文件,在里面敲上经典的C语言hello world代码,保存,进入cmd或powershell,移动到 hello.c 所在的文件夹(linux怎么做肯定不用我讲了,逃ε=ε=ε=┏(゜ロ゜;)┛)。

之后输入如下指令:

gcc -E hello.c -o hello.i
gcc -S hello.c -o hello.s

然后,打开 hello.i 我们就可以看到,预处理器已经对源码进行了修改(文件内容很多,我姑且放一张截图上来)

当然了,在文件的末尾是我们可怜的main函数:

如果想知道完整的文件里有什么,当然是自己动手试一下啦(✿◡‿◡)

然后,我们再打开 hello.s 文件,可以看到原始的 .c 被翻译为 .s 之后的结果:

(CSDN好像没有专门的汇编的渲染,姑且设置成C++)

	.file	"hello.c"
	.text
	.def	__main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
.LC0:
	.ascii "hello world\0"
	.text
	.globl	main
	.def	main;	.scl	2;	.type	32;	.endef
	.seh_proc	main
main:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$32, %rsp
	.seh_stackalloc	32
	.seh_endprologue
	call	__main
	leaq	.LC0(%rip), %rcx
	call	puts
	movl	$0, %eax
	addq	$32, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.ident	"GCC: (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) 8.1.0"
	.def	puts;	.scl	2;	.type	32;	.endef

接下来我们还可以用:

gcc hello.c -o hello.o

生成 .o 文件,不过到了这一步。这些东西就完全超出我的姿势范围了。


单个文件玩过了,当然还要玩一下多文件才过瘾,让我们准备三个文件: hell2.c func.c func.h ,内容依次如下:

// hello2.c
#include <stdio.h>
#include "func.h"

int main()
{
	say_hello();
}
// func.h
#ifndef FUNC_H
#define FUNC_H

#include <stdio.h>
void say_hello();

#endif
// func.c
#include "func.h"

void say_hello()
{
	printf("hello\n");
}

之后还是进入命令行模式,依次输入以下指令:

gcc -c func.c -o func.o
gcc -E hello2.c -o hello2.i
gcc -S hello2.c -o hello2.s
gcc hello2.c func.o -o hello2.exe

如果没有错误,我们将得到 func.c 编译生成的 .o 文件, hello2.c 经过预处理器和编译器处理之后得到的 .i 和 .s 文件,以及 hello2.c 最终生成的可执行文件。

打开 hello2.i ,我们将看到 #include "func.h" 被预处理器替换之后的情况:

// 上面省略100多行天书
# 1 "C:/mingw64/x86_64-w64-mingw32/include/_mingw_print_pop.h" 1 3
# 1400 "C:/mingw64/x86_64-w64-mingw32/include/stdio.h" 2 3
# 3 "hello2.c" 2
# 1 "func.h" 1






# 6 "func.h"
void say_hello();
# 4 "hello2.c" 2

int main()
{
 say_hello();
}

打开 hello2.s ,我们可以看到“精简”的汇编代码:

	.file	"hello2.c"
	.text
	.def	__main;	.scl	2;	.type	32;	.endef
	.globl	main
	.def	main;	.scl	2;	.type	32;	.endef
	.seh_proc	main
main:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$32, %rsp
	.seh_stackalloc	32
	.seh_endprologue
	call	__main
	call	say_hello
	movl	$0, %eax
	addq	$32, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.ident	"GCC: (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) 8.1.0"
	.def	say_hello;	.scl	2;	.type	32;	.endef

这篇博客到这里就差不多了,水平有限,欢迎指正!

= ̄ω ̄=

2011-12-27 17:59:21 derryzhang 阅读数 1415

考虑一下:

  • 将 Python 嵌入的到 C++ 中,编译出一个可执行程序
  • 并将 Python 的文件(.py, .so, .pyd,等)放到可执行程序所在目录的某个子目录

那么,嵌入的Python如何找到这些文件呢?

需要知道可执行程序自身路径,可是,C、C++ 标准库没有提供这种东西

只能使用系统api了,而系统api用起来需要注意的问题似乎总是不少,

不过呢,Qt 中提供的这种功能,我们不妨看看它是如何做的:

Qt Manual

QString QCoreApplication::applicationFilePath () [static]

Returns the file path of the application executable.

For example, if you have installed Qt in the /usr/local/qt directory, and you run the regexp example, this function will return "/usr/local/qt/examples/tools/regexp/regexp".

Warning: On Linux, this function will try to get the path from the /proc file system. If that fails, it assumes that argv[0] contains the absolute file name of the executable. The function also assumes that the current directory has not been changed by the application.

恩,这儿已经介绍了 Linux 下是如何实现的了,不过还是看看源码吧,毕竟,

在离开Qt的C++下,需要我们自己写类似的代码。

Unix/Linux

  • 如果是Linux,根据自身的pid,去查找 /proc/<pid>/exe

    • 找到,且是符号连接,那么返回其所指向的路径
  • 根据 命令行参数 argv[0] 进行判断
    • 如果以 “/” ,开头,那么就已经是绝对地址,直接返回
    • 否则 如果 包含 “/”,那么是相对地址,和工作路径合并后返回
    • 否则 遍历系统环境变量 PATH,查找其下和argv[0]同名的文件

源码如下:

#if defined( Q_OS_UNIX )
#  ifdef Q_OS_LINUX
    // Try looking for a /proc/<pid>/exe symlink first which points to
    // the absolute path of the executable
    QFileInfo pfi(QString::fromLatin1("/proc/%1/exe").arg(getpid()));
    if (pfi.exists() && pfi.isSymLink()) {
        d->cachedApplicationFilePath = pfi.canonicalFilePath();
        return d->cachedApplicationFilePath;
    }
#  endif

    QString argv0 = QFile::decodeName(QByteArray(argv()[0]));
    QString absPath;

    if (!argv0.isEmpty() && argv0.at(0) == QLatin1Char('/')) {
        /*
          If argv0 starts with a slash, it is already an absolute
          file path.
        */
        absPath = argv0;
    } else if (argv0.contains(QLatin1Char('/'))) {
        /*
          If argv0 contains one or more slashes, it is a file path
          relative to the current directory.
        */
        absPath = QDir::current().absoluteFilePath(argv0);
    } else {
        /*
          Otherwise, the file path has to be determined using the
          PATH environment variable.
        */
        QByteArray pEnv = qgetenv("PATH");
        QDir currentDir = QDir::current();
        QStringList paths = QString::fromLocal8Bit(pEnv.constData()).split(QLatin1Char(':'));
        for (QStringList::const_iterator p = paths.constBegin(); p != paths.constEnd(); ++p) {
            if ((*p).isEmpty())
                continue;
            QString candidate = currentDir.absoluteFilePath(*p + QLatin1Char('/') + argv0);
            QFileInfo candidate_fi(candidate);
            if (candidate_fi.exists() && 

Windows

Windows 提供了 GetModuleFileName 这种函数,所以操作就容易多了,不过Qt源码中也还是一大段哈。

  • 首先,假定路径总长度不超过 MAX_PATH(当前情况下,其值仍然是260,似乎微软也没有动它的打算?)
  • 在 栈 上分配一个 MAX_PATH + 2 长度的数组
  • 如果真实的长度超过了 MAX_PATH,就改用在 堆 中分配内存
    • 分配 2倍MAX_PATH,进行尝试
    • 依然不行,则分配 3倍 MAX_PATH,
    • ...

    // We do MAX_PATH + 2 here, and request with MAX_PATH + 1, so we can handle all paths
    // up to, and including MAX_PATH size perfectly fine with string termination, as well
    // as easily detect if the file path is indeed larger than MAX_PATH, in which case we
    // need to use the heap instead. This is a work-around, since contrary to what the
    // MSDN documentation states, GetModuleFileName sometimes doesn't set the
    // ERROR_INSUFFICIENT_BUFFER error number, and we thus cannot rely on this value if
    // GetModuleFileName(0, buffer, MAX_PATH) == MAX_PATH.
    // GetModuleFileName(0, buffer, MAX_PATH + 1) == MAX_PATH just means we hit the normal
    // file path limit, and we handle it normally, if the result is MAX_PATH + 1, we use
    // heap (even if the result _might_ be exactly MAX_PATH + 1, but that's ok).
    wchar_t buffer[MAX_PATH + 2];
    DWORD v = GetModuleFileName(0, buffer, MAX_PATH + 1);
    buffer[MAX_PATH + 1] = 0;

    if (v == 0)
        return QString();
    else if (v <= MAX_PATH)
        return QString::fromWCharArray(buffer);

    // MAX_PATH sized buffer wasn't large enough to contain the full path, use heap
    wchar_t *b = 0;
    int i = 1;
    size_t size;
    do {
        ++i;
        size = MAX_PATH * i;
        b = reinterpret_cast<wchar_t *>(realloc(b, (size + 1) * sizeof(wchar_t)));
        if (b)
            v = GetModuleFileName(NULL, b, size);
    } while (b && v == size);

    if (b)
        *(b + size) = 0;
    QString res = QString::fromWCharArray(b);
    free(b);
2011-08-14 09:28:00 iteye_6429 阅读数 11

考虑一下:

  • 将 Python 嵌入的到 C++ 中,编译出一个可执行程序
  • 并将 Python 的文件(.py, .so, .pyd,等)放到可执行程序所在目录的某个子目录

那么,嵌入的Python如何找到这些文件呢?

需要知道可执行程序自身路径,可是,C、C++ 标准库没有提供这种东西

只能使用系统api了,而系统api用起来需要注意的问题似乎总是不少,

不过呢,Qt 中提供的这种功能,我们不妨看看它是如何做的:

Qt Manual

QString QCoreApplication::applicationFilePath () [static]

Returns the file path of the application executable.

For example, if you have installed Qt in the /usr/local/qt directory, and you run the regexp example, this function will return "/usr/local/qt/examples/tools/regexp/regexp".

Warning: On Linux, this function will try to get the path from the /proc file system. If that fails, it assumes that argv[0] contains the absolute file name of the executable. The function also assumes that the current directory has not been changed by the application.

恩,这儿已经介绍了 Linux 下是如何实现的了,不过还是看看源码吧,毕竟,

在离开Qt的C++下,需要我们自己写类似的代码。

Unix/Linux

  • 如果是Linux,根据自身的pid,去查找/proc/<pid>/exe

    • 找到,且是符号连接,那么返回其所指向的路径
  • 根据 命令行参数 argv[0] 进行判断
    • 如果以 “/” ,开头,那么就已经是绝对地址,直接返回
    • 否则 如果 包含 “/”,那么是相对地址,和工作路径合并后返回
    • 否则 遍历系统环境变量 PATH,查找其下和argv[0]同名的文件

源码如下:

#if defined( Q_OS_UNIX )
#  ifdef Q_OS_LINUX
    // Try looking for a /proc/<pid>/exe symlink first which points to
    // the absolute path of the executable
    QFileInfo pfi(QString::fromLatin1("/proc/%1/exe").arg(getpid()));
    if (pfi.exists() && pfi.isSymLink()) {
        d->cachedApplicationFilePath = pfi.canonicalFilePath();
        return d->cachedApplicationFilePath;
    }
#  endif

    QString argv0 = QFile::decodeName(QByteArray(argv()[0]));
    QString absPath;

    if (!argv0.isEmpty() && argv0.at(0) == QLatin1Char('/')) {
        /*
          If argv0 starts with a slash, it is already an absolute
          file path.
        */
        absPath = argv0;
    } else if (argv0.contains(QLatin1Char('/'))) {
        /*
          If argv0 contains one or more slashes, it is a file path
          relative to the current directory.
        */
        absPath = QDir::current().absoluteFilePath(argv0);
    } else {
        /*
          Otherwise, the file path has to be determined using the
          PATH environment variable.
        */
        QByteArray pEnv = qgetenv("PATH");
        QDir currentDir = QDir::current();
        QStringList paths = QString::fromLocal8Bit(pEnv.constData()).split(QLatin1Char(':'));
        for (QStringList::const_iterator p = paths.constBegin(); p != paths.constEnd(); ++p) {
            if ((*p).isEmpty())
                continue;
            QString candidate = currentDir.absoluteFilePath(*p + QLatin1Char('/') + argv0);
            QFileInfo candidate_fi(candidate);
            if (candidate_fi.exists() && 

Windows

Windows 提供了 GetModuleFileName 这种函数,所以操作就容易多了,不过Qt源码中也还是一大段哈。

  • 首先,假定路径总长度不超过 MAX_PATH(当前情况下,其值仍然是260,似乎微软也没有动它的打算?)
  • 在 栈 上分配一个 MAX_PATH + 2 长度的数组
  • 如果真实的长度超过了 MAX_PATH,就改用在 堆 中分配内存
    • 分配 2倍MAX_PATH,进行尝试
    • 依然不行,则分配 3倍 MAX_PATH,
    • ...

    // We do MAX_PATH + 2 here, and request with MAX_PATH + 1, so we can handle all paths
    // up to, and including MAX_PATH size perfectly fine with string termination, as well
    // as easily detect if the file path is indeed larger than MAX_PATH, in which case we
    // need to use the heap instead. This is a work-around, since contrary to what the
    // MSDN documentation states, GetModuleFileName sometimes doesn't set the
    // ERROR_INSUFFICIENT_BUFFER error number, and we thus cannot rely on this value if
    // GetModuleFileName(0, buffer, MAX_PATH) == MAX_PATH.
    // GetModuleFileName(0, buffer, MAX_PATH + 1) == MAX_PATH just means we hit the normal
    // file path limit, and we handle it normally, if the result is MAX_PATH + 1, we use
    // heap (even if the result _might_ be exactly MAX_PATH + 1, but that's ok).
    wchar_t buffer[MAX_PATH + 2];
    DWORD v = GetModuleFileName(0, buffer, MAX_PATH + 1);
    buffer[MAX_PATH + 1] = 0;

    if (v == 0)
        return QString();
    else if (v <= MAX_PATH)
        return QString::fromWCharArray(buffer);

    // MAX_PATH sized buffer wasn't large enough to contain the full path, use heap
    wchar_t *b = 0;
    int i = 1;
    size_t size;
    do {
        ++i;
        size = MAX_PATH * i;
        b = reinterpret_cast<wchar_t *>(realloc(b, (size + 1) * sizeof(wchar_t)));
        if (b)
            v = GetModuleFileName(NULL, b, size);
    } while (b && v == size);

    if (b)
        *(b + size) = 0;
    QString res = QString::fromWCharArray(b);
    free(b);
2018-07-20 15:53:05 baidu_37964071 阅读数 2956

如下图所示:
这里写图片描述

详细解释

1、编译预处理

读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理

伪指令主要包括以下四个方面:
  1. 宏定义指令,如#define Name TokenString,#undef等。
    对于前一个伪指令,预编译所要做的是将程序中的所有Name用TokenString替换,但作为字符串常量的Name则不被替换。
    对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。
  2. 条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif,等等。
    这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉
  3. 头文件包含指令,如

    #include"FileName"
    #include<FileName>

    在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。
    采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。
    预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
    包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在/usr/include目录下。在程序中#include它们要使用尖括号(<>)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号(”“)。
  4. 特殊符号,预编译程序可以识别一些特殊的符号。
    例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。

2.编译阶段

经过预编译得到的输出文件中,将只有常量。如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,},+,-,*,\,等等。预编译程序所要做的工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

3.优化阶段

优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。
对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。

第二种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。

经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器执行。

4.汇编过程

汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。
对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。

目标文件由段组成。通常一个目标文件中至少有两个段:
  1. 代码段 ,该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。
  2. 数据段,主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

UNIX环境下主要有三种类型的目标文件:
(1)可重定位文件,其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。
(2)共享的目标文件,这种文件存放了适合于在两种上下文里链接的代码和数据。第一种事链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个目标文件;第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。
(3)可执行文件,它包含了一个可以被操作系统创建一个进程来执行之的文件。

汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。

5.链接程序

由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。
例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够让操作系统装入执行的统一整体。

根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:
  1. 静态链接
    在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。
    静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
    (注:静态链接将链接库的代码复制到可执行程序中,使得可执行程序体积变大)
  2. 动态链接
    在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。
    链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
    (注:动态链接指的是需要链接的代码放到一个共享对象中,共享对象映射到进程虚地址空间,链接程序记录可执行程序将来需要用的代码信息,根据这些信息迅速定位相应的代码片段。)

对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。

经过上述五个过程,C源程序就最终被转换成可执行文件了。缺省情况下这个可执行文件的名字被命名为a.out。

2013-10-30 09:20:33 csweier 阅读数 641
QString QCoreApplication::applicationFilePath () [static]

Returns the file path of the application executable.

For example, if you have installed Qt in the /usr/local/qt directory, and you run the regexp example, this function will return "/usr/local/qt/examples/tools/regexp/regexp".

Warning: On Linux, this function will try to get the path from the /proc file system. If that fails, it assumes that argv[0] contains the absolute file name of the executable. The function also assumes that the current directory has not been changed by the application.

C++中\变成\\

阅读数 1657

没有更多推荐了,返回首页