c++ helloworld_入门级的HelloWorld经历了哪些过程

纠正错误

        再开始正文之前,要纠正一个长久的错误认知。在刚刚入门 C 语言的时候,下载的 Microsoft Visual C++ 6.0 ,不是编译器,它的真实名字叫做集成开发环境。那么现在来看看,什么才是编译器。

编译器和链接器

        打开 Microsoft Visual C++ 6.0 安装目录下的 VC98 下的 Bin 目录,里面的 CL.EXE 是编译器,LINK.EXE 是链接器。选择 CL.EXE,发现只有 64kb

7a5c1297932ada34b0a1e5ce1242c945.png

        一个编译器难道只有 64kb ,这么大吗?答案肯定是:不是的。CL.EXE 只是编译器的 shell,只是一个外壳而已,相当于现实世界中的管家,它只接受命令,而真正做事的是 C1.DLL,C1XX.DLL,C2.DLL。

编写 Hello World

        编写 Hello World,命名为 hello.c

#include int main(){  printf("Hello world\r\n");  return 0;}

编译和链接

        接下来编译 hello.c 程序,打开命令行输入 cl /c hello.c 。/c 代表只编译不链接。执行命令后成功,编译为 hello.obj 文件。

549918f3b0b3639e651b9bda8c86df53.png

        cl 命令常用的还有,/W4 /WX

  • W4 VC提供了很多检查级别,级别越高检查越严格,默认是 1 ,W4 是 4 级检查

  • WX WX的意思是将 warning(警告)转变成 error(错误)

        因为代码定义的是 int main 所以必须返回整形,为了测试,所以将代码中的 return 0;改为 return 3.14;

#include int main(){  printf("Hello world\r\n");  return 3.14;}

        接下来打开命令行,仍输入 cl /c hello.c,命令执行成功,成功生成 hello.obj 文件。

24e8c1ffae8af360dbe96b703b8db633.png

        命令行输入 cl /c /W4 hello.c,编译仍然通过,可生成 hello.obj 文件,但是多了一处警告,warning C4244: 'return' : conversion from 'const double ' to 'int ', possible loss of data。警告 C4244,返回:从常量 double 转换到 int 可能数据丢失。

d6768ee87342b4493e978ea0995be2dc.png

        命令行输入 cl /c /W4 /WX hello.c,编译器没有通过。不但有一处警告还把警告当成了错误。

        hello.c(7) : error C2220: warning treated as error - no object file generated。错误 C220:把警告当成了错误,没有目的文件生成
        hello.c(7) : warning C4244: 'return' : conversion from 'const double ' to 'int ', possible loss of data。警告 C4244,返回:从常量 double 转换到 int 可能数据丢失。

2a73c751af334aff6e7803e1e23fe7c1.png

        为了让代码通过,把 return 3.14,进行强转

#include int main(){  printf("Hello world\r\n");  return (int)3.14;}

        保存后输入命令 cl /c /W4 /WX hello.c ,因为强转 (int)3.14 是预计之中的事情,所以成功编译通过,并生成 hello.obj 文件。

53eaa1710c224b8c3360514ae3801edc.png

        那么生成的 hello.obj 文件是什么呢?用 WinHex 打开 hello.obj 来进行查看。里面是满满的的二进制文件,在右侧可以看到字符串 Hello world 。所以得出结论,编译就是将人类阅读的文本代码转换为机器能理解的 2 进制代码。

55a4f45f6efcbcac34eb3a5fe61d3991.png

        虽然编译后是 2 进制代码,但机器仍然不可以执行。所以这时要用到 link ,输入命令 link hello.obj 。成功执行后生成了 hello.exe 。所以得出结论,链接就是从指定的 obj 文件中,抽取二进制代码,数据以及其他相关所需信息,按约定的操作系统中执行文件格式打造一个符合要求的执行文件。Windows 中可执行文件格式是 PE 格式,Linux 中可执行文件格式是 ELF 格式

675c6ad0c54e8f54a6b6701b76b4c7c2.png

Hello World 的运行过程

        include

        从第一行 include 说起,都知道它是包含某个文件,有时候见到的是 include<>,有时候见到的是 include"",但是编译之后发现效果是一样的。其实不同点只是查找顺序不一样,<> 代表官方文件,先找环境变量,后找程序所在目录。""代表自定义文件,先在文件所在目录进行寻找,后找环境变量。以<>为例,找到环境变量,看到有一个 include

35a12f5a498102f3cb43b9bf4702ef8f.png

        打开 include ,里面的内容为:

        D:\Microsoft Visual Studio\VC98\atl\include
        D:\Microsoft Visual Studio\VC98\mfc\include
        D:\Microsoft Visual Studio\VC98\include

        所以编译器会先在 D:\Microsoft Visual Studio\VC98\atl\include 找,有没有 stdio.h 。发现没有,继续在  D:\Microsoft Visual Studio\VC98\mfc\include 找有没有 stdio.h ,发现也没有。最终在  D:\Microsoft Visual Studio\VC98\include 找到了 stdio.h 。如果还没有找到 stdio.h 文件,编译器就会在程序所在目录进行寻找。如果还没找到程序就会报错。

c7511ca54b3b6fe2cd1ca35fbd45f1d7.png

        打开 stdio.h ,复制下来,将 hello.c 的第一行代码 include 替换为 stdio.h 中的代码

27959e45245f0ccae9d79da2b3072623.png

        输入命令 cl /c /W4 /WX hello.c 可成功编译,link hello.obj 也可成功链接,最后成功执行

d69b358ba586935a7c4d0acb9146f055.png

        进一步测试 include 做了什么事,新建一个 test.bmp 文件,里面的代码为:

printf("Hello world\r\n");return (int)3.14;

        hello.c 的代码为:

#include int main(){  #include "test.bmp"}

        输入命令 cl /c /W4 /WX hello.c 可成功编译,link hello.obj 也可成功链接,最后成功执行

c43dc4677720c7ded856603398c7f29d.png

        输入命令 cl /c /P hello.c ,/P 参数是将预处理器输出写入文件,打开预处理后生成的文件,截取其中需要分析的部分,将 test.tmp 中的代码复制粘贴到 hello.c 中。所以 include 的作用,就是把 include 指定的文件,粘贴复制到指定位置。

int main(){  #line 1 "test.bmp"printf("Hello world\r\n");return (int)3.14;#line 6 "hello.c"}

        main()

        main() 叫做程序入口是不对的。举一个反例,学过 C 的都知道,完整的 hello world 应该是:

#include int main(int argc, char argv[], char *envp[]){  printf("Hello world!\r\n");  return (int)3.14;}

        上面的代码,int main 有三个参数,如果 main() 是程序的入口,那三个参数又是从哪里来的呢?显然说 main() 是程序的入口是不对的

int main(int argc, char argv[], char *envp[])

        为了证明 main() 是程序的入口是不对的,以代码为证,找到 \VC98\CRT\SRC 下的 CRT0.C ,打开并找到 mainCRTStartup 函数。CRT 是 C Run Time 的缩写,mainCRTStartup 也就是 main 函数运行时的启动函数,大概来读一读这个程序,首先获取操作系统的版本,然后通过位运算,获得他的主版本号和次版本号,初始化堆环境,初始化IO,取得命令行,取得环境变量,格式化命令行,格式化环境变量,初始化浮点寄存器以及初始化全局变量,根据编码方式选择对应窗口或控制台,然后才到 main 函数

void mainCRTStartup(#endif  /* WPRFLAG */#endif  /* _WINMAIN_ */        void        ){        int mainret;#ifdef _WINMAIN_        _TUCHAR *lpszCommandLine;        STARTUPINFO StartupInfo;#endif  /* _WINMAIN_ */        /*         * Get the full Win32 version         */        _osver = GetVersion();        _winminor = (_osver >> 8) & 0x00FF ;        _winmajor = _osver & 0x00FF ;        _winver = (_winmajor << 8) + _winminor;        _osver = (_osver >> 16) & 0x00FFFF ;#ifdef _MT        if ( !_heap_init(1) )               /* initialize heap */#else  /* _MT */        if ( !_heap_init(0) )               /* initialize heap */#endif  /* _MT */            fast_error_exit(_RT_HEAPINIT);  /* write message and die */#ifdef _MT        if( !_mtinit() )                    /* initialize multi-thread */            fast_error_exit(_RT_THREAD);    /* write message and die */#endif  /* _MT */        /*         * Guard the remainder of the initialization code and the call         * to user's main, or WinMain, function in a __try/__except         * statement.         */        __try {            _ioinit();                      /* initialize lowio */#ifdef WPRFLAG            /* get wide cmd line info */            _wcmdln = (wchar_t *)__crtGetCommandLineW();            /* get wide environ info */            _wenvptr = (wchar_t *)__crtGetEnvironmentStringsW();            _wsetargv();            _wsetenvp();#else  /* WPRFLAG */            /* get cmd line info */            _acmdln = (char *)GetCommandLineA();            /* get environ info */            _aenvptr = (char *)__crtGetEnvironmentStringsA();            _setargv();            _setenvp();#endif  /* WPRFLAG */            _cinit();                       /* do C data initialize */#ifdef _WINMAIN_            StartupInfo.dwFlags = 0;            GetStartupInfo( &StartupInfo );#ifdef WPRFLAG            lpszCommandLine = _wwincmdln();            mainret = wWinMain(#else  /* WPRFLAG */            lpszCommandLine = _wincmdln();            mainret = WinMain(#endif  /* WPRFLAG */                               GetModuleHandleA(NULL),                               NULL,                               lpszCommandLine,                               StartupInfo.dwFlags & STARTF_USESHOWWINDOW                                    ? StartupInfo.wShowWindow                                    : SW_SHOWDEFAULT                             );#else  /* _WINMAIN_ */#ifdef WPRFLAG            __winitenv = _wenviron;            mainret = wmain(__argc, __wargv, _wenviron);#else  /* WPRFLAG */            __initenv = _environ;            mainret = main(__argc, __argv, _environ);#endif  /* WPRFLAG */#endif  /* _WINMAIN_ */            exit(mainret);        }        __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )        {            /*             * Should never reach here             */            _exit( GetExceptionCode() );        } /* end of try - except */}

       printf

        下面来说一说 printf ,printf 最基本的功能就是输出,但是 printf 的特性是,格式化输出到标准输出设备上(默认是显示器)。

          编程过的都知道,printf 可以接收不定参数, 也就是1 个或多个参数,例如下面这个程序

#include int main(int argc, char argv[], char *envp[]){  printf("%sHello %fworld!%c%d\r\n", "haha", 3.14, 'a', 1);  return (int)3.14;}

编译链接生成可执行程序后的结果就是:hahaHello 3.140000world!a1

81d1757acd48748d04e712b00c49cccd.png

        为了证明 printf 可以接受不定参数,打开 stdio.h ,并找到 printf 函数声明,可以看到 const char *,这是固定的必须接受一个参数,... 代表接受不定参数

_CRTIMP int __cdecl printf(const char *, ...);

        那 printf 的结果是如何输出到显示屏的呢?好多人认为,C 语言直接操纵了底层,printf 操作了显存,其实是错误的,操作显存是操作系统的事情。printf 只是激活协调操作系统的功能,按操作系统的要求,把输出内容提交给操作系统,而操作系统再来操作显存。在现代的操作系统中,是有 3 环(用户应用程序(最低特权))和 0 环(内核(最高特权))的区别的。printf 只是一个工作在 3环的函数。也就是所有和硬件访问相关的,包括上面说的显存,都是 0 环做的事情,现在的操作系统不允许用户的程序真正的影响到硬件。就好比你(3 环)去肯德基吃汉堡,肯德基不会允许客户进厨房(0 环)的,如果要吃汉堡或鸡翅,只能通过工作人员(接口),等做好了,你来取餐就可以了。其中厨房的火候,料理(0 环),你(3 环)是接触不到的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值