输出当前程序的代码

输出当前程序的代码

问题

和白博聊天,白博提了一个问题,一个程序输出其代码本身。

在和白博的交流中,想到一个比较取巧的办法,就是直接读取源代码,然后输出。

先给出代码

#include<iostream>
#include<fstream>
#include<vector>
#include<string.h>
#include<sstream>

using namespace std;
int main(void)
{
    //打开文件
    ifstream infile(__FILE__);
    if (!infile)
        return -1;

    string strAll;
    ostringstream sin;
    sin << infile.rdbuf();
    strAll = sin.str();
    cout << strAll;
    return 0;
}

运行结果为

运行结果

理论

这样做的理论基础是,由于程序在由源代码到可执行文件的转变过程后与最初的.cpp文件已经没有关系了

一般来说这个过程分为编译和链接两个部分,如下图

理论

编译阶段将源程序(*.c) 转换成为目标代码(一般是obj文件,至于具体过程就是上面说的那些),链接阶段是把源程序转换成的目标代码(obj文件)与你程序里面调用的库函数对应的代码连接起来形成对应的可执行文件(exe文件)就可以了

编译预处理

预处理的过程主要处理包括以下过程:

将所有的#define删除,并且展开所有的宏定义

处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等

处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。

删除所有注释 “//”和”/ /”.

添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。

保留所有的#pragma编译器指令,因为编译器需要使用它们

命令为 gcc-E hello.c hello.i

这里 -E 是只进行预处理

编译

编译过程就是把预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。

现在版本的GCC把预处理和编译两个步骤合成一个步骤,用cc1工具来完成。gcc其实是后台程序的一些包装,根据不同参数去调用其他的实际处理程序,比如:预编译编译程序cc1、汇编器as、连接器ld

汇编

把经过编译的.s文件转化为机器码 .o 文件 .obj文件也是

链接

链接分为静态链接和动态链接。

静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。

而动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。

链接的主要内容是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确地衔接。

链接的主要过程包括:地址和空间分配(Address and Storage Allocation),符号决议(Symbol Resolution),重定位(Relocation)等。

在这之后就能产生可以运行的exe文件了

VS中的编译链接

但是在目前大多用的集成度比较高的IDE的情况下,很多步骤都被继承在了一起,例如在VS中按下F5一切都OK了

打开安装目录下的VSDIR\VC\bin可以看到一系列的可执行程序.exe和批处理文件,这些就是构建、编译、链接时要用到的工具。看一下几个主要的工具:

cl.exe:编译程序

link.exe:链接程序

lib.exe:加载lib库的程序

nmake.exe:用makefile进行构建、编译的工具

再回到问题

我们之前采用的方法有些讨巧,但反而具有了通用性,可以将其封装为一个函数,在任意的一个代码文件中均可以输出当前文件的代码

int PutsSelfCode()
{
    ifstream infile(__FILE__);
    if (!infile)
        return -1;

    string strAll;
    ostringstream sin;
    sin << infile.rdbuf();
    strAll = sin.str();
    cout << strAll;
    return 0;
}

对于其他方法,这里可以参见这篇博客