1、GCC的发展
- GNU(意为非洲牛羚)项目,又称革奴计划,是由Richard Stallman在1983创办。
- 1985年,Richard Stallman又创立了自由软件基金会(Free Software Foundation, 简称FSF) 来为GNU提供技术、法律以及财政支持。
- 编译器GCC就是GNU开发出来的一款编译器软件,GCC是GNU CC的简称。
- GCC符合ANSI C标准,能够编译C、C++、Object C等语言编写的程序。GCC还是一个交叉平台编译器,能够在当前CPU平台为多种不同架构的硬件平台开发软件,因此适合嵌入式领域的开发编译。
- GCC是免费的,可移植。
2、gcc的语法结构
gcc 的基本语法: :::info gcc [options] [filenames] :::
- 常用编译选项
-c
:只编译不连接,生成目标文件".o"
。-S
:只编译不汇编,生成汇编代码。-E
:只进行预编译,不做其他处理。(相当于头文件与宏定义处理)-o file
:指定输出文件(一般就是二进制的文件)-v
:打印出编译器内部编译过程的信息和编译器的版本。-std=name
:指定C语言的标准(比如C99)-l dir
:在头文件的搜索路径列表中添加dir目录。
练习:新建本节文件目录结构如下,我们将使用 GCC 编译器对src/hello.c
进行编译。
b07@SB:~/c/chapter2$ tree
.
├── bin
├── include
├── obj
└── src
└── hello.c
4 directories, 1 file
b07@SB:~/c/chapter2$ cat ./src/hello.c
#include <stdio.h>
int main() {
printf("Hello World\n");
return 0;
}
# 1. 查看 GCC 版本
b07@SB:~/c/chapter2$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.5.0-3ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
# 2. 默认 gcc 编译标准(此处为 2011)
b07@SB:~/c/chapter2$ gcc -E -dM - </dev/null | grep "STDC_VERSION"
#define __STDC_VERSION__ 201112L
# 3. 编译链接一气呵成
b07@SB:~/c/chapter2$ gcc -o ./bin/hello ./src/hello.c
b07@SB:~/c/chapter2$ ls bin
hello
b07@SB:~/c/chapter2$ ./bin/hello # 必须使用 ./ 表明当前路径下
Hello World
# 4. 编译目标文件和指定编译标准 -std
b07@SB:~/c/chapter2$ gcc -std=c99 -o obj/hello.o -c src/hello.c # -std必须在-o前面,-c只编译不链接
b07@SB:~/c/chapter2$ ls obj/
hello.o
b07@SB:~/c/chapter2$ gcc -o bin/hello2 obj/hello.o # 目标文件再进行链接
b07@SB:~/c/chapter2$ ./bin/hello2
Hello World
- 优化选项:
-O
:减小代码的长度和执行时间,效果等价于-O1
,其中包括线程跳转和延迟退栈。-O2
:除完成所有的-O1
级别优化,还进行一些额外的调整工作,如处理器指令调度等。(一般都是这个)-O3
:除完成所有-O2
级别优化,还包括循环展开和一些其他与处理器特性相关的优化工作。(用的不多,编译时间长)
数字越大速度越快。一般使用-O2
,它在编译时间和代码大小之间取得了相对平衡。我们以一个程序演示这几个比优化选项的差别。
b07@SB:~/c/chapter2$ cat src/optimize.c
/*
* filename: optimize.c
* description: 通过 GCC 命令的选项 -O(1/2/3) 对循环程序进行优化
*/
#include <stdio.h>
int main() {
double counter, result, temp;
for (counter = 0; counter < 4000.0 * 4000.0 * 4000.0 / 20.0 + 2030;
counter += (5 -3 + 2 + 1) / 4) {
temp = counter / 1239;
result = counter;
}
printf("Result is %lf\n", result);
return 0;
}
# 不优化运行时间
b07@SB:~/c/chapter2$ gcc -o bin/optimizeO1 src/optimize.c
b07@SB:~/c/chapter2$ time ./bin/optimizeO1
Result is 3200002029.000000
real 0m10.675s
user 0m10.672s
sys 0m0.000s
# 2. -O1 优化运行时间
b07@SB:~/c/chapter2$ gcc -O -o bin/optimizeO1 src/optimize.c
b07@SB:~/c/chapter2$ time ./bin/optimizeO1
Result is 3200002029.000000
real 0m4.508s
user 0m4.484s
sys 0m0.000s
# 2. -O2 优化允许时间
b07@SB:~/c/chapter2$ gcc -O2 -o bin/optimizeO2 src/optimize.c
b07@SB:~/c/chapter2$ time ./bin/optimizeO2
Result is 3200002029.000000
real 0m4.742s
user 0m4.719s
sys 0m0.000s
# 3. -O3 优化允许时间
b07@SB:~/c/chapter2$ gcc -O3 -o bin/optimizeO3 src/optimize.c
b07@SB:~/c/chapter2$ time ./bin/optimizeO3
Result is 3200002029.000000
real 0m4.531s
user 0m4.531s
sys 0m0.000s
实验结果可以看到优化比不优化的差距很大,虽然优化级别之间运行时间相差不大,但是一般使用-O2
优化。
- 警告和出错选项
-ansi
:支持符合ANSI标准的 C 程序即 C90-pedantic
:允许发出 ANSI C 标准所列的全部警告信息。-pedantic-error
:允许发出 ANSI C 标准所列的全部错误信息。-w
:关闭所有的警告。-Wall
:允许发出gcc所提供的所有有用的警告信息(比较关键,对于养成良好的习惯) ```c1. 无警告信息
b07@SB:~/c/chapter2$ cat ./src/hello.cinclude
int main() { printf(“Hello World\n”); return 0; } b07@SB:~/c/chapter2$ gcc -Wall -o bin/hello_world src/hello.c # 无警告信息 b07@SB:~/c/chapter2$ ./bin/hello_world # 正常运行 Hello World
2. 警告信息(hello.c中的 main 函数去掉 return 0;)
b07@SB:~/c/chapter2$ cat src/hello.c
include
int main() { printf(“Hellow World\n”); } b07@SB:~/c/chapter2$ gcc -o bin/hello_world src/hello.c # 默认不警告 b07@SB:~/c/chapter2$ ./bin/hello_world Hello World b07@SB:~/c/chapter2$ gcc -Wall -o bin/hello_world src/hello.c # 加上 -Wall 为什么不警告? b07@SB:~/c/chapter2$ ./bin/hello_world Hello World
:::warning
**Q:**为什么上面没有警告信息输出呢?难道是现在编译器能够容忍你的一些比较常见的错误吗?<br />A:
:::
什么是 Error,Error 分为语法错误和语义错误两种:
1. **语法错误**:即违反 C 语言规定语法,是无法通过编译生成可执行文件。这是与 warning 最大区别,因其可生成可执行文件。
1. **语义错误**:即**代码逻辑错误**,编译通过且程序能运行,但是得不到正确结果,例如公式写错或整数除法。这种只能通过调试代码才能进行辨别。
```bash
# 1. Error 是无法通过编译生成可执行文件
b07@SB:~/c/chapter2$ cat ./src/hello.c
#include <stdio.h>
int main() {
printf("Hello World\n");
retur 0;
}
b07@SB:~/c/chapter2$ ls bin/ # bin 目录是空的
b07@SB:~/c/chapter2$ ls
bin include obj src
b07@SB:~/c/chapter2$ gcc -Wall -o bin/hello_world src/hello.c
src/hello.c: In function ‘main’:
src/hello.c:5:5: error: ‘retur’ undeclared (first use in this function)
retur 0;
^~~~~
src/hello.c:5:5: note: each undeclared identifier is reported only once for each function it appears in
src/hello.c:5:11: error: expected ‘;’ before numeric constant
retur 0;
^
b07@SB:~/c/chapter2$ ls bin/ # 依然是空的!
# 2. 语义错误 1-99 求和(整数除法错误)
b07@SB:~/c/chapter2$ cat ./src/semanticsError.c
#include <stdio.h>
int main() {
printf("1+2+...+99=%d\n", 99 / 2 * (1 + 99)); // 整数除法是向下整除(即取商舍余 99/2=49)
return 0;
}
b07@SB:~/c/chapter2$ gcc -Wall -o bin/semanticsError src/semanticsError.c
b07@SB:~/c/chapter2$ ./bin/semanticsError
1+2+...+99=4900
b07@SB:~/c/chapter2$ cat ./src/semanticsError.c
#include <stdio.h>
int main() {
printf("1+2+...+99=%d\n", 99 * (1 + 99) / 2); // 改变除法顺序即可得到正确结果
return 0;
}
b07@SB:~/c/chapter2$ gcc -Wall -o bin/semanticsError src/semanticsError.c
b07@SB:~/c/chapter2$ ./bin/semanticsError
1+2+...+99=4950
- 制作库文件选项
-L dir
:在库文件的搜索路径列表中添加dir目录-static
:静态链接库。-iname
:连接名为name的库文件-shared
:表明是使用共用库。
附录
1. Linux 环境搭建
Linux 上开发 C 语言可能对很多零基础的同学来说实在太难,因为他们都不熟悉 Linux,而且更畏惧 vi 编辑器。
- 编译器:默认情况 Linux 发行版没有安装 GCC 编译器,需要使用包管理器安装。
- 文本编辑器:这个东西就是写代码用的,类似 Windows 上的记事本程序,Linux 上默认自带 vi 编辑器,但是建议升级为 vim(vim 是其升级版,支持更多人性化操作,但是也不够人性!)
[
](https://www.cnblogs.com/wangxiaobei2019/p/12084735.html)
在使用 vim 进行文档操作时,经常需要跨应用进行复制粘贴,在粘贴大量代码时,出现行错位等各种错乱,查找问题解决办法:
- 一次性:vim 进入文件后,先 ESC 在出入
:set paste
回车后再按下i
之后进行粘贴的内容就规矩了 - 永久性:将当前配置写入 vim 的配置文件中,建议在用户家目录(不必改变 etc 目录下的 vim 系统配置)下创建
~/.vimrc
并复制以下内容即可。set encoding=utf-8
set nocompatible
syntax on
set noerrorbells
set nu
set tabstop=4
set shiftwidth=4
set softtabstop=4
set expandtab
set smartindent
set nowrap
set smartcase
set noswapfile
set nobackup
set undodir=~/.vim/undodir
set undofile
set incsearch
set paste
2. windows 环境搭建
建议 Windows 10 用户使用 Windows Subsystem for Linux(WSL),然后使用 VS Code 即可快速搭建开发环境。
下面使用的是 sublime text3 + MinGW,然后有能力的可以购买 sublime text3,打开速度比 VS Code 快,唯一缺点就是自带终端无法接受输入输出函数,且对小型文件或者头文件一起编译有点麻烦。
2.1 安装编译器
下载完 MinGW 是一个包管理器,需要下载编译器,一般选择三个即可,如下图所示,找不到可在 All packages
中寻找(一般可能下载失败跟网速有关),右键选中后点击 Aplly Changes
,最后不要忘记把这个下载目录添加进环境变量!!
比如我这里是D:\mingw_compiler\bin
该文件下面就有gcc程序,添加进环境变量则有path=D:\mingw_compiler\bin;
。然后如果你能在终端输入gcc -v
而不是系统无法找到指定程序错误就是环境安排好了。
C:\Users\15117>gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=d:/mingw_compiler/bin/../libexec/gcc/mingw32/9.2.0/lto-wrapper.exe
Target: mingw32
Configured with: ../src/gcc-9.2.0/configure --build=x86_64-pc-linux-gnu --host=mingw32 --target=mingw32 --disable-win32-registry --with-arch=i586 --with-tune=generic --enable-static --enable-shared --enable-threads --enable-languages=c,c++,objc,obj-c++,fortran,ada --with-dwarf2 --disable-sjlj-exceptions --enable-version-specific-runtime-libs --enable-libgomp --disable-libvtv --with-libiconv-prefix=/mingw --with-libintl-prefix=/mingw --enable-libstdcxx-debug --disable-build-format-warnings --prefix=/mingw --with-gmp=/mingw --with-mpfr=/mingw --with-mpc=/mingw --with-isl=/mingw --enable-nls --with-pkgversion='MinGW.org GCC Build-20200227-1'
Thread model: win32
gcc version 9.2.0 (MinGW.org GCC Build-20200227-1)
2.2 sublime text3 配置编译系统
其实这不是什么破玩意,上面才是最重要的,有能力的可以跳过这个配置编译系统,因为默认的 sublime text3 是包含有C的系统的(如下图),但是就是它一个自带终端毛病,使得输入无法执行,因此一般需要配置在 cmd 执行的编译系统。
点击最后的New Build System
创建,输入以下内容(百度很久都没法解决,下面是完全可以解决在 cmd 中文乱码问题,强制编译语言集为 gbk 解决)
{
"cmd": ["gcc","-Wall", "${file}","-std=c99", "-o", "${file_path}/${file_base_name}"],
"file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
"shell":true,
"working_dir": "${file_path}",
"selector": "source.c, source.c++, source.h",
"encoding":"gbk",
"variants":[
{
"name": "Run",
"cmd": ["cmd", "/c", "gcc", "-fexec-charset=gbk", "-Wall", "-D_GNU_SOURCE", "${file}",
"-std=c99","-o", "${file_path}/${file_base_name}",
"&&", "cmd", "/c", "${file_path}/${file_base_name}"]
},
{
"name": "RunInCommand",
"cmd": ["cmd", "/c", "gcc", "-Wall", "-fexec-charset=gbk", "-D_GNU_SOURCE","${file}",
"-std=c99","-o", "${file_path}/${file_base_name}",
"&&", "start", "cmd", "/c", "${file_path}/${file_base_name} & echo.&pause"]
}
]
}
目前你可以不知道这些是什么意思,但是你应该能动在Linux中手动 gcc -o bin/helloc src/hello.c
这样的格式!
"cmd":
这里不是cmd,而是sublime text3的终端,即第一个命令就是只编译。"variants"
:就是sublime text3的编译系统选择参数:"name":"Run"
:就是使用Windows的cmd运行然后把结果放在sublime text3的终端显示。"name":"RunInCommand"
:就是打开Windows的cmd运行与交互,最后关闭cmd后回到sublime text3编辑器。2.3 运行 C 程序
第一步编辑 C 程序,让我们写那个老掉牙的Hello Shit world
然后在sublime text3中按下(默认快捷键,修改另说),有如下三个选项,分别对应上面编译系统的三个命令+参数。#include <stdio.h> // 输入输出头文件(printf函数)
int main() {
printf("你好 Shit world"); // 我是注释
return 0; // main函数返回值
}
只编译:什么都没有输出,但是编译。在当前这个文件下有一个和文件名(不包括后缀名).exe文件。如果你直接点击,你眼速快可以看到一闪而过,因为程序执行完了!而使用拖入 cmd 中就可以看到具体结果,也就是为啥
variant
最后加上一个pause
.C:\Users\15117>D:\C语言\C学习\0.gcc的介绍使用\src\hello.exe
浣犲ソ Shit world # 乱码(因为第一个没指定编码格式)
编译 + sublime 终端输出:这个只要记住,在sublime下面黑框框输出就行,不管那么多。并且记住无法执行
scanf
输入!
:::tips
这个不需要添加
gbk
编码都可以在这个黑框框输出正确的中文,因为 sublime text 任何语言都可以。(有兴趣的可以试下,这里编译系统实现 gbk 强制编码)。
看到最后的计时没?很方便,但是tm的跟在输出的后面,解决这个问题吗,网上搜了麻烦,还不如在函数prinft
后面手动添加一个换行符\n
,并且prinft
函数自动补全也很机智的。
:::
- 编译 + cmd 终端输出:这主要就是弥补黑框没法识别输入函数而定制针对 cmd 特定 gbk 编码的选项。
因为中文操作系统的 cmd 很傻逼,不可以更改字符页,每次修改都是手动的,而且有修改注册表的,非常丑,尤其是最后带到 sublime text3 的终端黑框加上一个cha 956
(举例而已)