当前位置: 首页 > news >正文

《第一节:跟着符映维学C语言---配置c语言开发环境》

《跟着符映维学c语言--配置c语言开发环境》
今天,我们来学习基于debian配置c语言开发环境
先写出我们今天要讲解的内容大纲

功能需求:配置c语言开发环境和学会如何编译

  1. 系统环境,debian
  2. 终端工具,tmux(可选)
  3. 编译器,gcc
  4. 文本编辑工具,vim
  5. 简单的hello.c代码
  6. 使用gcc编译hello.c为可执行文件
  7. 在debian中运行可执行文件
  8. 了解gcc的编译过程
  9. 使用gcc进行分阶段编译
  10. gcc的常见编译选项
  11. 多文件编译
  12. 静态库创建与使用
  13. 动态库创建与使用
  14. 调试编译
  15. 交叉编译
  16. 依赖检查
  17. 常见的问题排查

然后我们对以上的大纲进行模块化

一、系统环境,debian

  1. 安装debian
    • 我们打开 https://www.debian.org/distrib/官网相关下载路径,
      选择下载 64 位 PC DVD-1 iso
    • 下载完成后,我们就可以把iso写入u盘,制作启动盘安装debian
    • 具体安装步骤我这里就不一一讲解,具体的详细安装过程请自行查询

二、终端工具,tmux(可选)

  1. 安装tmux

    sudo apt update && sudo apt install tmux
    
  2. 验证安装

    tmux -V
    
  3. 安装完成后我们创建会话

    tmux new -s fyw
    

三、编译器,gcc

  1. 安装gcc
    安装gcc,我们有两种安装选择.
    • (1)安装标准库和相关的工具
    sudo apt update && sudo apt install build-essential
    
    • (2)只安装基础标准c库
    sudo apt update && sudo apt install libc6-dev
    

注意:
如果安装了build-essential就不需要再安装libc6-dev
因为build-essential已经包含了libc6-dev

  1. 验证安装
gcc --version

四、文本编辑工具,vim

  1. 安装vim
    一般在我们安装好debian的时候,vim是debian自带的,可以直接使用.
sudo update && sudo apt install vim

如果想需要更多的功能支持,可以

sudo update && sudo apt install vim-nox

vim-nox 是vim的一个无gui的功能增强版本

  1. 验证安装
vim --version

五、简单的hello.c代码

#include <stdio.h>
int main(void){printf("hello,world!");return 0;
}

六、使用gcc编译hello.c为可执行文件

gcc hello.c -o hello

七、在debian中运行可执行文件

./hello

八、了解gcc的编译过程

gcc的编译过程有4个阶段

  1. 预处理阶段
  2. 编译阶段
  3. 汇编阶段
  4. 链接阶段

九、使用gcc进行分阶段编译

  1. 预处理阶段
gcc -E hello.c -o hello.i
  1. 编译阶段
gcc -S hello.i -o hello.s
  1. 汇编阶段
gcc -c hello.s -o hello.o
  1. 链接阶段
gcc hello.o -o hello

以上的分阶段编译后,我们就中以通过以下命令运行可执行文件

./hello

十、gcc的常见编译选项

  1. -Wall: 启用所有警告信息
  2. Wextra: 启用额外警告
  3. -Werror: 将警告视为错误
  4. -g: 生成调试信息
  5. -o: 指定输出文件名
  6. -O0 -O1 -O2 -O3 -Os: 指定优化级别
  7. -E:只运行预处理器
  8. -S:生成汇编代码
  9. -c:只编译不链接
  10. -lm: 链接数学库
  11. -fsanitize=address: 检测内存错误

现在我们通过实际操作直观的去理解以上常见的编译选项.

(1)-Wall

我们写一个main.c代码

#include <stdio.h>
int add(int a,int b){return a+b;
}
int main(void){int a=5,b=3;int result=add(a,b);if(result=8){        //正确应该是result==8printf("ok!\n");}else{printf("no!\n");}return 0;
}

以上是一个存在错误的c语言代码.我们进行编译

gcc main.c -o main
./main

我们发现,gcc正常编译生成可执行文件
输出

ok!

这是我们不想看到的,因为main.c是一个存在错误的代码.
我们希望的是gcc进行编译的时候,能捕获相关的错误代码.这时候,我们可以使用-Wall选项

gcc -Wall main.c -o main

编译后,返回

main.c: In function ‘main’:
main.c:8:12: warning: suggest parentheses around\assignment used as truth value [-Wparentheses]8 |         if(result=8){|            ^~~~~~

我们看到,gcc捕获了到了相关的错误代码.并提示我们.

(2)-Wextra

我们把以上的main.c代码改为

#include <stdio.h>
int main(void){int x=5;unsigned int y=3;if(x<y){        //有符号和无符号进行比较printf("x<y\n");}else{printf("x>y\n");}return 0;
}

编译

gcc -Wall main.c -o main

我们发现,gcc正常编译并生成了可执行文件.无法捕获有符号和无符号进行比较.
现在我们添加-Wextra选项进行编译

gcc -Wall -Wextra main.c -o main

返回

main.c: In function ‘main’:
main.c:5:13: warning: comparison of integer expressions\of different signedness: ‘int’ and ‘unsigned int’ [-Wsign-compare]5 |         if(x<y){|             ^

gcc捕获到了有符号和无符号比较的问题.

(3)-Werror

我们把以上的main.c代码改为.

#include <stdio.h>
int main(void){int c=10;int a=5,b=3;int result=a+b;printf("a+b=%d\n",result);return 0;
}

编译

gcc -Wall -Wextra main.c -o main

返回

main.c: In function ‘main’:
main.c:3:13: warning: unused variable ‘c’ [-Wunused-variable]3 |         int c=10;|             ^

gcc提示了一个警告warning,但不是错误.我们添加选项-Werror进行编译

gcc -Wall -Wextra -Werror main.c -o main

返回

main.c: In function ‘main’:
main.c:3:13: error: unused variable ‘c’ [-Werror=unused-variable]3 |         int c=10;|             ^
cc1: all warnings being treated as errors

我们看到,所有警告都被视为了错误error

(4)-g

我们把main.c改为

#include <stdio.h>
int main(void){printf("hello!\n");return 0;
}

编译

gcc -Wall -Wextra -Werror main.c -o main

我们使用file查看main,返回

main: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV)\
, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2\
, BuildID[sha1]=3d29aa44da638e2807fe7887acc1427248572d35\
, for GNU/Linux 3.2.0, not stripped

我们看到,生成的可执行文件main并没有包含调试信息,我们添加-g进行编译

gcc -Wall -Wextra -Werror -g main.c -o main

我们再使用file查看main,返回

main: ELF 64-bit LSB pie executable, x86-64,\version 1 (SYSV), dynamically linked, interpreter\/lib64/ld-linux-x86-64.so.2, BuildID\
[sha1]=eced594d38356600bdd56b0015cc601f47af61bd,\for GNU/Linux 3.2.0, with debug_info, not stripped

我们看到,这次的返回信息中包含了with debug_info,说明已经生成了调试信息.

(5)-o

-o为指定输出文件名,如: gcc main.c -o main,main就为输出的可执行文件名.

(6)-O0 -O1 -O2 -O3 -Os

-O0 -O1 -O2 -O3 -Os为优化级别选项,这里我们通过main.c通过不同的优化级别,
生成汇编代码,然后进行比较.来直观的去看他们的优化级别.
我们把main.c改为

#include <stdio.h>
int main(void){printf("hello\n");return 0;
}

然后编译

gcc -S -O0 main.c -o mainO0.s
gcc -S -O1 main.c -o mainO1.s
gcc -S -O2 main.c -o mainO2.s
gcc -S -O3 main.c -o mainO3.s
gcc -S -Os main.c -o mainOs.s

我们使用vim进行比较

vim -d mainO0.s mainO1.s

在vim中,会高亮显示两个文件差异的地方.具体的汇编区别,这里不进行详细解释.
这里我们需要明白的是,不同的优化级别,所执行的编译方式会有所不同.

(7)(8)(9)-E -S -c

-E -S -c 分别生成预处理.i文件,编译.s文件,和汇编.o文件
我们进行编译查看区别

gcc -E main.c 
gcc -S main.c
gcc -c main.c

我们看到,不同的选项生成了相应的文件.

(10)-lm

我们把以上main.c改为

#include <stdio.h>
#include <math.h>
int main(void){int a=6;int result=sqrt(a);printf("sqrt(a)=%d\n",result);return 0;
}

编译

gcc main.c -o main

返回

/usr/bin/ld: /tmp/ccByXknH.o: in function `main':
main.c:(.text+0x23): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status

我们看到提示了错误,这个错误是因为我们没有链接数学库造成的.我们添加-lm选项再次编译

gcc main.c -o main -lm

我们看到,gcc成功编译为可执行文件.

(11)-fsanitize=address

我们把以上main.c改为

#include <stdio.h>
int main(void){int arr[5]={1,2,3,4,5};arr[5]=6;        //越界printf("%d\n",arr[5]);return 0;
}

编译

gcc main.c -o main

我们看到gcc编译成功,我们运行可执行文件 ./main,返回

6

这显示是错误的,而且这种错误我们不容易发现.但gcc并没有捕获到这个边界问题.
我们添加编译选项-fsanitize=address再次编译

gcc -fsanitize=address main.c -o main

我们看到gcc编译成功,我们运行可执行文件 ./main 返回..

==109849==ERROR: AddressSanitizer:\stack-buffer-overflow on address 0x7fff9451dc34 at pc 0x561
48dfcf389 bp 0x7fff9451dbf0 sp 0x7fff9451dbe8                                                 
WRITE of size 4 at 0x7fff9451dc34 thread T0                                                   #0 0x56148dfcf388 in main\(/home/fuyingwei/document/main/test/testdddd/main+0x1388)       #1 0x7fe1e9e46249 in\__libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58     #2 0x7fe1e9e46304 in __libc_start_main_impl ../csu/libc-start.c:360                       #3 0x56148dfcf0d0 in _start\(/home/fuyingwei/document/main/test/testdddd/main+0x10d0)     Address 0x7fff9451dc34 is located in stack of thread T0 at offset 52 in frame                 #0 0x56148dfcf1a8 in main (/home/fuyingwei/document/main/test/testdddd/main+0x11a8)       This frame has 1 object(s):                                                                 [32, 52) 'arr' (line 3) <== Memory access at offset 52 overflows this variable
HINT: this may be a false positive\if your program uses some custom stack unwind mechanism, swapcontext or vfork(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer:\stack-buffer-overflow\(/home/fuyingwei/document/main/test/testdddd/main+0x1388) in main
Shadow bytes around the buggy address:0x10007289bb30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x10007289bb40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x10007289bb50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x10007289bb60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x10007289bb70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10007289bb80: f1 f1 f1 f1 00 00[04]f3 f3 f3 f3 f3 00 00 00 000x10007289bb90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x10007289bba0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x10007289bbb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x10007289bbc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x10007289bbd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):Addressable:           00Partially addressable: 01 02 03 04 05 06 07 Heap left redzone:       faFreed heap region:       fdStack left redzone:      f1Stack mid redzone:       f2Stack right redzone:     f3Stack after return:      f5Stack use after scope:   f8Global redzone:          f9Global init order:       f6Poisoned by user:        f7Container overflow:      fcArray cookie:            acIntra object redzone:    bbASan internal:           feLeft alloca redzone:     caRight alloca redzone:    cb
==109849==ABORTING

我们看到ASan捕获到了这个边界问题,并提示了我们.

十一、多文件编译

我们创建以下的目录文件结构

project/|---main.c|---utils.h|---utils.c
  • utils.h
#ifndef UTILS_H
#define UTILS_H
int add(int a,int b);
#endif
  • utils.c
#include "utils.h"
int add(int a,int b){return a+b;
}
  • main.c
#include <stdio.h>
#include "utils.h"
int main(void){int a=5,b=3;int result=add(a,b);printf("a+b=%d\n",result);return 0;
}

多文件编译的方法
方法一:分开编译

gcc main.c
gcc utils.c
gcc main.o utils.o hello

方法二:直接编译

gcc main.c utils.c -o hello

十二、静态库创建与使用

  1. 静态库创建
gcc -c utils.c -o utils.o
ar rcs libutils.a utils.o
gcc main.c -L. -lutils -o static_hello
  1. 运行程序
./hello

提示: 静态库所创建的可执行文件,不会受库的影响,也就是说
当我们使用静态库创建可执行文件后,对静态库进行改名或删除,可执行文件都能运行

十三、动态库创建与使用

  1. 动态库创建与使用
gcc -c -fPIC utils.c -o utils.o
gcc -shared -o libutils.so utils.o
gcc main.c -L. -lutils -o dynamic_hello
  1. 运行程序
expore LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./hello

注意: 动态库所创建的可执行文件,受动态库的影响,如果修改动态库的名称或删除,
都会导致可执行文件无法运行.

十四、调试编译

在gcc中,调试编译的选项是-g,我们在检查一个可执行文件的时候,我们可以通过
file检查是否包含调试信息

file 可执行文件

如果返回的信息中包含debug_info 那么说明可执行文件包含可调试信息.
如果没有,我们可以

gcc -g main.c -o main

包含调试信息后,我们就可以通过gdb进行调试.关于gdb的调试,后面会说.这里先不赘述.

十五、交叉编译

交叉编译是指在当前平台中,生成目标平台(另一个平台)可运行的代码文件.
比如我们要生成在ARM64架构的程序.

  1. 安装适合我们系统环境的相关编译包
sudo apt update && sudo apt aarch64-linux-gnu-gcc
  1. 编译目标平台的可运行代码文件
aarch64-linux-gnu-gcc main.c -o main

我们用file查看,会看到返回包含/lib/ld-linux-aarch64.so.1的信息.

十六、依赖检查

  1. 基本依赖检查
    我们可以这样
gcc -M main.c
  1. 排除系统头文件的依赖
gcc -MM main.c
  1. 动态依赖检查
    当我们用file查看可执行文件,如果包含类似/lib64/ld-linux-x86-64.so.2的信息,
    我们就可以
ldd 可执行文件

查看相关的动态依赖,返回类似的信息

        linux-vdso.so.1 (0x00007fff32992000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe50829e000)/lib64/ld-linux-x86-64.so.2 (0x00007fe50849e000)
  1. 预处理依赖检查
    我们可以使用
gcc -H main.c

查看预处理相关的依赖文件路径

十七、gcc常见问题排查

1. 编译错误类问题

(1)"No such file or directory" 错误

  • 问题:找不到头文件或源文件
  • 可能原因
    • 头文件路径未正确设置
    • 文件名拼写错误
    • 文件确实不存在
  • 错误:
    fatal error: xxx.h: No such file or directory
    
  • 解决方案:
    # 使用-I选项指定头文件路径
    gcc -I/path/to/include -o output source.c
    

 sudo apt update && sudo apt install build-essential

(2)未定义的引用错误 (Undefined reference)

  • 问题:链接时找不到函数或变量定义
  • 可能原因
    • 未链接必要的库
    • 函数/变量名拼写错误
    • 库文件顺序不正确
  • 错误:
    undefined reference to 'function_name'
    
  • 解决方案
    # 使用-l选项链接库,注意库的顺序
    gcc -o program source.c -lm -lxyz
    

(3)版本不兼容错误

  • 问题:代码使用新特性但编译器版本过旧
  • 错误:
    error: 'for' loop initial declarations are only allowed in C99 mode
    note: use option -std=c99 or -std=gnu99 to compile your code
    
  • 解决方案
    # 检查GCC版本
    gcc --version# 使用特定标准编译
    gcc -std=c11 -o output source.c
    

2. 警告类问题

(1)隐式声明警告 (Implicit declaration)

  • 问题:函数未声明就使用
  • 警告:
    warning: implicit declaration of function
    

'func_name' [-Wimplicit-function-declaration]

- **解决方案**:
- 包含正确的头文件
- 在使用前声明函数原型**(2)未使用变量警告 (Unused variable)**
- **问题**:定义了但未使用的变量
- **警告**:

warning: unused variable 'var_name' [-Wunused-variable]

- **解决方案**:
- 删除无用变量
- 使用 `(void)variable;` 显式忽略
- 或添加 `-Wno-unused-variable` 关闭此警告
```bash
gcc -Wno-unused-variable -o program source.c

3. 链接问题

(1)库路径问题

  • 问题:找不到库文件
  • 错误:
    /usr/bin/ld: cannot find -lxyz
    
  • 解决方案
    # 使用-L指定库路径
    gcc -L/path/to/libs -o program source.c -lxyz
    

(2)静态库与动态库冲突

  • 问题:同时存在同名的静态库(.a)和动态库(.so)
  • 错误:
    /usr/bin/ld: warning: libxyz.so, needed by ..., may conflict with libxyz.a
    
  • 解决方案
    • 明确指定要链接的库类型
    • 使用 -static 强制静态链接

4. 运行时问题

(1)动态链接库找不到

  • 问题:运行时找不到.so文件
  • 错误:
    error while loading shared libraries: libxyz.so: 
    

cannot open shared object file: No such file or directory

- **解决方案**:
```bash
# 设置LD_LIBRARY_PATH环境变量
export LD_LIBRARY_PATH=/path/to/libs:$LD_LIBRARY_PATH# 或者使用-rpath选项
gcc -Wl,-rpath,/path/to/libs -o program source.c -lxyz

(1)段错误 (Segmentation fault)

  • 问题:访问非法内存
  • 错误:
    Segmentation fault (core dumped)
    
  • 解决方案
    • 使用 -g 编译并调试
    gcc -g -o program source.c
    gdb ./program
    
    • 使用地址检查工具如Valgrind

5. 优化问题

(1)优化导致程序行为异常

  • 问题:使用-O2/-O3优化后程序出错
  • 错误:
    Program behaves differently with -O2/-O3 optimization
    
  • 解决方案
    • 检查代码中是否有未定义行为
    • 逐步增加优化级别定位问题
    • 使用 -fno-strict-aliasing 等选项关闭特定优化

6. 其他常见错误

(1)语法错误 (Syntax error)

  • 错误:
    error: expected ';' before '}' token
    
  • 问题:缺少分号、括号不匹配等基本语法错误
  • 解决方案
    1. 检查错误指示行附近是否缺少分号
    2. 检查所有括号是否成对出现
    3. 使用代码编辑器的括号匹配功能辅助检查
    4. 示例修正:
      // 错误示例
      int a = 1
      // 修正后
      int a = 1;
      

(2)类型不匹配 (Type mismatch)

  • 错误:
    error: incompatible types when assigning to type 'int' from type 'float'
    
  • 问题:变量类型不兼容的赋值或操作
  • 解决方案
    1. 检查变量类型声明
    2. 必要时进行显式类型转换
    3. 示例修正:
      // 错误示例
      int a = 3.14;
      // 修正后
      int a = (int)3.14;
      

**(3)多重定义 (Multiple definition)

  • 错误:
    /usr/bin/ld: main.o:/path/to/file.h:23: 
    

multiple definition of 'global_var'; first defined here

- **问题**:全局变量在多个文件中定义
- **解决方案**:
1. 在头文件中使用 `extern` 声明变量
2. 在单个源文件中定义变量
3. 示例修正:```c// file.hextern int global_var;  // 声明// file.cint global_var = 0;     // 定义```**(4)重定义 (Redefinition)**
- **原错误**:

error: redefinition of 'struct_name'

- **问题**:结构体/类型重复定义
- **解决方案**:
1. 使用头文件保护宏
2. 检查是否在不同地方重复定义相同结构体
3. 示例修正:```c// file.h#ifndef FILE_H#define FILE_Hstruct MyStruct {int a;};#endif```**(5)缺少返回语句 (Missing return)**
- **原错误**:

warning: control reaches end of non-void function [-Wreturn-type]

- **问题**:非void函数可能没有返回值
- **解决方案**:
1. 确保所有执行路径都有返回值
2. 检查函数逻辑是否完整
3. 示例修正:```c// 错误示例int func(int x) {if(x > 0) return 1;}// 修正后int func(int x) {if(x > 0) return 1;return 0;}```### 额外建议:
1. 对于复杂错误,使用 `-Wall -Wextra` 开启更多警告
2. 使用 `-Werror` 将警告视为错误,强制修正
3. 分步编译定位问题:```bashgcc -E source.c > preprocessed.c  # 预处理gcc -S source.c                   # 生成汇编gcc -c source.c                   # 只编译不链接
http://www.njgz.com.cn/news/49.html

相关文章:

  • 再见,大连
  • 影视软件集合分享
  • 7.26总结
  • geogebra 2 进阶
  • 20250726GF模拟赛
  • java学习
  • 深入解析Passkeys背后的密码学原理
  • CCF中学生计算机程序设计-基础篇-第一章-函数练习答案
  • 第二次总结——关系中的魔法语言
  • 2025.7 Solar应急响应-
  • 【计算几何】Largest Quadrilateral
  • 2025暑假qbxtNOIP实战梳理营Day1题目
  • 请求类型绑定响应类型
  • Untitled-1
  • AI代理性能提升实战:LangChain+LangGraph内存管理与上下文优化完整指南
  • GAIA基准测试介绍
  • 多项式全家桶(wjc)
  • 暑假qbxtNOIP实战梳理营Day1题目
  • 7月26日
  • 韦东山:嵌入式Linux全新系列教程之驱动大全(基于IMX6ULL开发板) 视频+资料(60G) 价值1299元
  • ARC200 小记
  • java第二十六天
  • 咕咕嘎嘎!!!(hard)
  • 主流PLC串口自由协议通信标准化
  • 20250726
  • Abp vNext -动态 C# API 实现原理解析
  • 健身营养——Stan Efferding
  • 20250726-31
  • Linux 如何统计系统上各个用户登录(或者登出)记录出现的次数?
  • ThreadLocal