Windows 与 Linux 下 C 编译工具链对比MSVC MinGW GCC本文系统梳理Windows平台下的 MSVC和MinGW与Linux平台下的GCC的区别。工具链全称 / 背景所属生态主要平台MSVCMicrosoft Visual C Compiler微软官方WindowsMinGWMinimalist GNU for WindowsGNU 开源社区WindowsGCCGNU Compiler CollectionGNU/Linux 开源生态LinuxMinGW本质上是在Windows运行的GCC将Linux/的 GCC 工具链移植到Windows并提供对Windows API的访问能力三者都经历预处理 → 编译 → 汇编 → 链接四个阶段但工具和输出格式存在差异步骤MSVC (Windows)MinGW (Windows)GCC (Linux)编译器前端cl.exegcc.exegcc汇编器内嵌于cl.exeasGNU asas链接器link.exeldGNU ldld目标文件格式COFF.objCOFF使用.o扩展名ELF.o可执行文件格式PE.exePE.exeELF通常无扩展名静态库.lib.a.a动态库.dll.lib导入库.dll.dll.a或.lib.so要注意MSVC和MinGW都生成.dll但他们不能混用它们使用不同的运行时库CRT和 ABI应用二进制接口混合链接会导致崩溃。工具链运行时库是否需要额外安装部署便利性MSVCMSVCRT如msvcr140.dll,vcruntime140.dll需安装对应版本的Visual C Redistributable较麻烦需分发运行时MinGWmsvcrt.dllWindows 系统自带或静态链接通常无需额外安装尤其静态链接时更轻量、独立GCC (Linux)glibc系统自带一般已存在依赖系统版本方面MSVCMinGWGCC (Linux)IDE 支持深度集成 Visual Studio调试强大Code::Blocks、Qt Creator、VS CodeVim/Emacs GDB或 VS Code、CLion调试器cdb/ Visual Studio Debuggergdbgdb构建系统MSBuild、CMake支持Make、CMakeMake、CMake、Meson 等标准支持C20/23 支持较好随 VS 更新依赖 GCC 版本如 MinGW-w64 GCC 13最新标准支持快开源社区活跃跨平台性仅 Windows可跨平台原生跨平台编译单个文件# MSVC-在 Developer Command Prompt 中cl hello.c# MinGWgcc hello.c -o hello.exe# Linux GCCgcc hello.c -o hello笔者使用的编译器为MinGwGCC环境为VScode or Debian 12.04.源文件头文件介绍C语言把.c为后缀的文件称为源文件把.h为后缀的文件称为头文件。第一个C语言程序#include stdio.hint main(void){printf(hello world!\n);return 0;}5.main函数main函数是程序的入口因此被称为主函数无论这个C语言程序有多少行代码均从main函数开始执行。int表示main在执行结束时需要返回一个整型类型的值这与结尾的return 0相互呼应。即使一个项目有多个.c文件也只可以有一个main函数6.printf和库函数上面第一个C语言程序中包含了一个代码printf(hello world!\n);此处使用了printf函数实现在屏幕上打印的功能。printf是一个库函数,printf也可以打印其他类型的数据int n 100;printf(%d\n,n); //打印整型printf(%c\n,q); //打印字符串printf(%lf\n,3.14); //打印双精度浮点行这里的%d %c是占位符会被后面对应的值替换此处只做初步了解。在使用库函数的时候必须包含其对应的头文件比如printf函数需要包含stdio.h这个头文件使用库函数的方法是添加#include stdio.h为什么需要库函数呢为了不在重复实现常用的代码C语言标准规定了一组函数用于提升开发效率由不同的编译器厂商根据标准实现这些函数库叫做标准库有一些厂商会自行添加函数为了代码的移植性和跨平台开发我们应该主动使用标准库中的函数7.C语言中的关键字在C语言有一些符号名称被保留下来诸如int,if,return,goto等等关键字具有特殊含义人为创建标识符的时候不能与关键字重复关键字也不可以被创建C语言中最通用普遍的关键字一共有32个1.auto break case char const continue default do double else enumextern2.float for goto if int long register return short signed sizeofstatic3.struct switch typedef union unsigned void volatile whileC99中加入了_Bool_Complex_Imaginaryinlinerestrict。C11中加入了_Alignas,_Alignof,_Atomic,_Static_assert,_Noreturn,_Thread_local,generic。8.字符和ASCII编码诸如:,!,n,p,%,等这样的符号叫做字符在C中这些字符使用单引号括起来的像这样X。在计算机中所有的数据都是以二进制存储的这些字符在内存中如何用二进制存储美国国家标准学会(ANSI)指定了标准ASCII编码C中的字符就遵循ASCII编码的方式。我们只需要能够查阅表格即可但为了一些场景使用方便我们最好记住一些特殊的数字字符A-Z的码值在65-90字符a-z的码值在97-122大小写字符A-Z,a-z的码值差值是32数字字符0~9的码值在48-57换行\n的码值为100~31码值对应的字符不可被打印打印单个字符可以用占位符%c指定其格式#include stdio.hint main(void){printf(%c\n,X);printf(%c\n,88);return 0;}在屏幕上输出所有的可打印字符#include stdio.hint main(void){int i 0;for (i 32; i 127;i){printf(%c ,i);if (i % 16 15)printf(\n);}return 0 ;}这样会在终端上输出所有的可打印字符。当然不能少了编译的过程。9.字符串和隐藏的\0我们已经知道了字符这个概念如果是很多字符连接起来叫做什么在C语言中表示字符串使用:abcdefg使用双引号括起来的字符叫做字符串像一串“烧烤”打印字符串可以用占位符%s指定其格式也可以直接打印字符串:#include stdio.hint main(void){printf(%s\n,abcdefg);printf(abcdefg);return0;}在字符串中其实隐藏了一个\0字符这个\0放置在字符串的末尾\0是字符串结束的标志。例如字符串abcdefg显示出来7个字符但在末尾还有一个隐藏的\0,使用printf函数打印字符串或者使用serlen()函数计算字符串长度的时候遇到\0就停止因此\0是字符串的结束标志在C语言中也可以把一个字符串放在字符数组里来验证\0的作用:#include stdio.hint main(void){char arr1[] {a,b,c};char arr2[] {abc};printf(%s\n,arr1);printf(%s\n,arr2);return 0;}在进行调试的过程中可以观察到arr1和arr2中内容的差异arr1中只有a,b,c。而arr2中则有a,b,c,\0。由于笔者暂时还不会在Linux中调试因此脑补一下吧滑跪--如果运行这段代码可能会产生这样的现象abc烫烫烫烫烫烫?n?abc这是因为没有\0仍然向后读取内存中的内容具体什么内容那就是未知数了。arr2数组因为使用了字符串常量初始化因此一切正常。若在arr1中单独加入\0则一切也会正常由此可见\0的作用10.转义字符在上文中使用了\0,\n这些也是字符但他们是一组特殊的字符成为转义字符用来转变原来字符的意思。在屏幕中打印出n,很显然#include stdio.hint main(void){printf(abcdnefg);return 0;}会输出abcdnefg如果在n之前加入\则#include stdio.hint main(void){printf(abcd\nefg);return 0;}会输出abcdefg显然两次输出的结果差异很大\n这个转义字符表示换行在C语言中的转义字符还有\a响铃Bell产生 audible 或 visible 警告如终端 beep\b退格Backspace光标回退一格\f换页Form feed用于打印机或终端清屏在某些终端等效于清屏\n换行Newline将光标移到下一行开头\r回车Carriage return将光标移至当前行开头不换行\t水平制表符Horizontal tab通常跳到下一个 8 字符/4 字符对齐位置\v垂直制表符Vertical tab在现代终端中较少使用\\反斜杠字符本身用于表示字面量\\单引号字符用于字符常量中如\\双引号字符用于字符串中包含引号如He said \Hi\\?问号字符用于避免与三字符组 trigraphs 冲突现已很少用\0空字符Null characterASCII 值为 0常用于字符串结尾\ooo1~3 位八进制数表示的字符如\141表示 a\xhh1~2 位十六进制数表示的字符如\x61表示 a用代码可以演示#include stdio.hint main(void){printf(%c\n,\);printf(%s\n,\);printf(d:\\C_code\\gitee\\test.c\n);printf(\a);printf(%c\n,\130);//130是八进制输出Xprintf(%c\n,\x58);//x30是十六进制输出Xreturn 0;}11.语句及其分类C语言的代码是由语句构成可以分为五大类:空语句表达式语句函数调用语句复合语句控制语句11.1空语句极其简单的语句一个分号就是空语句#include stdio.hint main(void){;//此处为空语句return 0;}出现在此处需要一个语句但是不需要它做事情。11.2表达式语句在表达式的后面加上分号#include stdio.hint main(void){int a 10;int b 20;b a 5;//此处为表达式语句return 0;}11.3函数调用语句//一个文件中包含两个函数#include stdio.hvoid butler(void);//ANSI/ISO C函数原型,这里是函数原型int main(void)//这里是以函数调用的形式出现在main()中{printf(I will summon the butler function.\n);butler();//函数调用语句printf(Yes. Bring me some tea and writeable DVDs.\n);//函数调用语句return 0;}void butler(void)//函数定义开始{printf(You rang, sir?\n);}11.4复合语句复合语句也就是代码块成对括号里的代码就构成一个代码块诸如函数中的代码块#include stdio.hint main(void){printf(程序开始\n);{ // 这是一个复合语句代码块int x 10;int y 20;printf(x y %d\n, x y);} // x 和 y 的作用域在此结束printf(程序结束\n);return 0;}if语句中的代码块#include stdio.hint main(void){int score 85;if (score 60){printf(及格了\n);printf(继续加油\n);} // 这个 {} 构成一个复合语句return 0;}循环中的代码块#include stdio.hint main(void){for (int i 0; i 3; i){printf(第 %d 次循环\n, i 1);int temp i * 2;printf(temp %d\n, temp);} // 整个循环体是一个复合语句return 0;}嵌套代码块#include stdio.hint main(void){int a 1;{int b 2;printf(a %d, b %d\n, a, b);{int c 3;printf(a %d, b %d, c %d\n, a, b, c);} // c 的作用域结束} // b 的作用域结束// 此处只能访问 aprintf(回到外层a %d\n, a);return 0;}广泛用于函数体、控制结构if/for/while 等中11.5控制语句控制语句用于控制程序的执行以实现程序中的各种结构方式在C语言中支持三种结构顺序结构选择结构循环结构。他们由特定的语法定义符号组成。在C语言中由九种控制语句可以分为三大类:条件判断语句if,swith循环语句:do while,while,for转向语句break,goto,continue,return此处只作列举学习留在后面。