Windows 与 Linux 下 C 编译工具链对比:MSVC MinGW GCC
本文系统梳理Windows平台下的 MSVC和MinGW与Linux平台下的GCC的区别。
| 工具链 | 全称 / 背景 | 所属生态 | 主要平台 |
|---|---|---|---|
| MSVC | Microsoft Visual C++ Compiler | 微软官方 | Windows |
| MinGW | Minimalist GNU for Windows | GNU 开源社区 | Windows |
| GCC | GNU Compiler Collection | GNU/Linux 开源生态 | Linux |
MinGW本质上是在Windows运行的GCC,将Linux/的 GCC 工具链移植到Windows,并提供对Windows API的访问能力
三者都经历预处理 → 编译 → 汇编 → 链接四个阶段,但工具和输出格式存在差异:
| 步骤 | MSVC (Windows) | MinGW (Windows) | GCC (Linux) |
|---|---|---|---|
| 编译器前端 | cl.exe | gcc.exe | gcc |
| 汇编器 | 内嵌于cl.exe | as(GNU as) | as |
| 链接器 | link.exe | ld(GNU ld) | ld |
| 目标文件格式 | COFF(.obj) | COFF(使用.o扩展名) | ELF(.o) |
| 可执行文件格式 | PE(.exe) | PE(.exe) | ELF(通常无扩展名) |
| 静态库 | .lib | .a | .a |
| 动态库 | .dll+.lib(导入库) | .dll+.dll.a或.lib | .so |
要注意MSVC和MinGW都生成.dll,但他们不能混用,它们使用不同的运行时库(CRT)和 ABI(应用二进制接口)混合链接会导致崩溃。
| 工具链 | 运行时库 | 是否需要额外安装? | 部署便利性 |
|---|---|---|---|
| MSVC | MSVCRT(如msvcr140.dll,vcruntime140.dll) | 需安装对应版本的Visual C++ Redistributable | 较麻烦(需分发运行时) |
| MinGW | msvcrt.dll(Windows 系统自带)或静态链接 | 通常无需额外安装(尤其静态链接时) | 更轻量、独立 |
| GCC (Linux) | glibc(系统自带) | 一般已存在 | 依赖系统版本 |
| 方面 | MSVC | MinGW | GCC (Linux) |
|---|---|---|---|
| IDE 支持 | 深度集成 Visual Studio(调试强大) | Code::Blocks、Qt Creator、VS Code | Vim/Emacs + GDB,或 VS Code、CLion |
| 调试器 | cdb/ Visual Studio Debugger | gdb | gdb |
| 构建系统 | MSBuild、CMake(支持) | Make、CMake | Make、CMake、Meson 等 |
| 标准支持 | C++20/23 支持较好(随 VS 更新) | 依赖 GCC 版本(如 MinGW-w64 GCC 13) | 最新标准支持快(开源社区活跃) |
| 跨平台性 | 仅 Windows | 可跨平台 | 原生跨平台 |
编译单个文件
# MSVC-在 Developer Command Prompt 中 |
cl hello.c |
# MinGW |
gcc hello.c -o hello.exe |
# Linux GCC |
gcc hello.c -o hello |
笔者使用的编译器为MinGw&GCC,环境为VScode or Debian 12.0
4.源文件,头文件介绍
C语言把.c为后缀的文件称为源文件,把.h为后缀的文件称为头文件。
第一个C语言程序
#include <stdio.h> |
int 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 enum |
extern |
2.float for goto if int long register return short signed sizeof |
static |
3.struct switch typedef union unsigned void volatile while |
C99中加入了_Bool,_Complex,_Imaginary,inline,restrict。
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的码值为10 - 0~31码值对应的字符不可被打印
打印单个字符可以用占位符%c指定其格式:
#include <stdio.h> |
int main(void) |
{ |
printf("%c\n",'X'); |
printf("%c\n",88); |
return 0; |
} |
在屏幕上输出所有的可打印字符:
#include <stdio.h> |
int 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.h> |
int main(void) |
{ |
printf("%s\n","abcdefg"); |
printf("abcdefg"); |
return0; |
} |
在字符串中,其实隐藏了一个\0字符,这个\0放置在字符串的末尾,\0是字符串结束的标志。
例如字符串"abcdefg",显示出来7个字符,但在末尾还有一个隐藏的\0,使用printf()函数打印字符串或者使用serlen()函数计算字符串长度的时候,遇到\0就停止,因此\0是字符串的结束标志
在C语言中也可以把一个字符串放在字符数组里来验证\0的作用:
#include <stdio.h> |
int 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.h> |
int main(void) |
{ |
printf("abcdnefg"); |
return 0; |
} |
会输出:
abcdnefg |
如果在n之前加入\,则:
#include <stdio.h> |
int main(void) |
{ |
printf("abcd\nefg"); |
return 0; |
} |
会输出:
abcd |
efg |
显然两次输出的结果差异很大\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 character),ASCII 值为 0,常用于字符串结尾\ooo:1~3 位八进制数表示的字符(如\141表示 'a')\xhh:1~2 位十六进制数表示的字符(如\x61表示 'a')
用代码可以演示:
#include <stdio.h> |
int main(void) |
{ |
printf("%c\n",'\''); |
printf("%s\n","\""); |
printf("d:\\C_code\\gitee\\test.c\n"); |
printf("\a"); |
printf("%c\n",'\130');//130是八进制,输出'X' |
printf("%c\n",'\x58');//x30是十六进制,输出'X' |
return 0; |
} |
11.语句及其分类
C语言的代码是由语句构成,可以分为五大类:
- 空语句
- 表达式语句
- 函数调用语句
- 复合语句
- 控制语句
11.1空语句
极其简单的语句,一个分号就是空语句:
#include <stdio.h> |
int main(void) |
{ |
;//此处为空语句 |
return 0; |
} |
出现在此处需要一个语句,但是不需要它做事情。
11.2表达式语句
在表达式的后面加上分号:
#include <stdio.h> |
int main(void) |
{ |
int a = 10; |
int b = 20; |
b = a + 5;//此处为表达式语句 |
return 0; |
} |
11.3函数调用语句
//一个文件中包含两个函数 |
#include <stdio.h> |
void 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.h> |
int 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.h> |
int main(void) |
{ |
int score = 85; |
if (score >= 60) |
{ |
printf("及格了!\n"); |
printf("继续加油!\n"); |
} // 这个 {} 构成一个复合语句 |
return 0; |
} |
循环中的代码块:
#include <stdio.h> |
int 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.h> |
int 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 的作用域结束 |
// 此处只能访问 a |
printf("回到外层,a = %d\n", a); |
return 0; |
} |
广泛用于函数体、控制结构(if/for/while 等)中
11.5控制语句
控制语句用于控制程序的执行,以实现程序中的各种结构方式,在C语言中,支持三种结构:顺序结构,选择结构,循环结构。他们由特定的语法定义符号组成。
在C语言中由九种控制语句,可以分为三大类:
- 条件判断语句:
if,swith - 循环语句:
do while,while,for - 转向语句:
break,goto,continue,return
此处只作列举,学习留在后面。