• Linux学习笔记——C程序的编译运行与调试


    这篇文章比较详细地介绍了gcc工具的基本使用,下面主要对一些内容进行补充。



    gcc基本使用

    gcc 在这里代指 gcc编译工具链,一个 .c 文件需要经过如下四个步骤生成最终的可执行文件

    我们可以一步步执行命令生成可执行文件,也可以直接使用 gcc hello.c -o a.out 生成可执行文件。

    下图展现了gcc常用的一些参数

    • -I 指定头文件目录

    hello.c

    #include
    #include "hello.h"
    
    int main()
    {
            int a = NUM;
            printf("Hello %d!\n",a);
            return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    hello.h

    #ifndef NUM
    #define NUM 20
    #endif
    
    • 1
    • 2
    • 3

    一般都将头文件放入 include 文件夹,那么编译生成可执行文件需要指定头文件目录:

    gcc hello.c -Iinclude -o a.out
    
    • 1
    • -D 编译时定义宏

    我们可以在编译时指定宏来实现代码的注释与否以进行DEBUG等行为。

    hello.c

    #include
    #include "hello.h"
    
    int main()
    {
            int a = NUM;
            printf("Hello %d!\n",a);
            #ifdef DEBUG
            printf("DEBUG\n");
            #endif
            return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    • -On n=0~3 进行编译优化

    test.c

    gcc -S test.c -o test.s
    test.s


    gcc -S test.c -O3 -o test.s

    可见编译优化后将上面复杂代码优化为 int d = 10


    静态库

    正常开发一个软件需要将头文件放进 include 文件夹,源文件放入 src 文件夹,并可以将源程序编译生成的多个可执行文件放入函数库中,这样在给用户程序时就可以不需要提交源代码。

    而函数库分为静态库与动态库(共享库),下面首先介绍静态库。

    目录结构如下:

    main.c

    #include 
    #include "main.h"
    
    int main()
    {
            int a = 10;
            int b = 5;
            printf("%d %d\n", add(a,b), sub(a,b));
            return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    main.h

    #ifndef MAIN_H
    #define MAIN_H
    int add(int x, int y);
    int sub(int x, int y);
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5

    acc.c

    #include "main.h"
    int add(int x, int y)
    {
            return x + y;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    sub.c

    #include "main.h"
    int sub(int x, int y)
    {
            return x - y;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    gcc *.c -c -I./include 生成对应.o文件
    ar rcs libmytest.a *.o 生成静态库libmytest.a
    gcc main.c -L./lib -lmytest -Iinclude -o app 生成可执行文件app


    动态库(共享库)

    除了静态库,还有动态库,其各有各自的优点和缺点。

    目录结构如下:

    • gcc -fPIC *.c -c -Iinclude 生成与位置无关的目标文件,这是因为动态库的目标文件是运行时动态加载进目标程序中,所以需要目标文件与位置无关,即相对位置。
    • gcc -shared -o libmytest.so *.o 制作动态库
    • gcc main.c -L./lib -lmytest -I./include -o app 使用动态库
    • .app 执行生成的可执行文件
      这是就有问题出现了,app运行失败,我们也可以 ldd app 发现 libmytest 找不到

      这是因为没有给动态链接器(ld-linux.so.2)指定好动态库libmytest.so的路径。

    这里有四个解决方案,第一个方案是临时方案,二至四个方法都是永久设置,其中永久设置最好使用第四种方法。

    1. 临时设置:export LD_LIBRARY_PATH=库路径,将当前目录加入环境变量,但是终端退出了就无效了。
    2. 永久设置:将上条写入家目录下.bashrc文件中
    3. 粗暴设置:直接将libmytest.so文件拷贝到/usr/lib/【/lib】目录下。(受libc库的启发)
    4. libmytest.so 所在目录的绝对路径追加入到 /etc/ld.so.conf 文件,
      使用 sudo ldconfig -v 更新

    gdb调试

    getmin.h

    #ifndef GETMIN_H
    #define GETMIN_H
    int getmin(int a[], int l, int r);
    #endif
    
    • 1
    • 2
    • 3
    • 4

    getmin.c

    #include "getmin.h"
    int getmin(int a[], int l, int r)
    {
            int i;
            int mi = a[l];
            int pos = l;
            for(i=l+1; i<r; ++i)
            {
                    if(a[i] < mi)
                    {
                            mi = a[i];
                            pos = i;
                    }
            }
            return pos;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    sort.h

    #ifndef SORT_H
    #define SORT_H
    void sort(int a[], int n);
    #endif
    
    • 1
    • 2
    • 3
    • 4

    sort.c

    #include "getmin.h"
    void sort(int a[], int n)
    {
            int i;
            for(i=0; i<n; ++i)
            {
                    int pos = getmin(a, i, n);
                    int temp = a[pos];
                    a[pos] = a[i];
                    a[i] = temp;
            }
            return ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    main.c

    #include 
    #include "sort.h"
    
    int main()
    {
            int a[10] = {5, 1, 3, 2, 4, 6, 9, 0, 8, 7};
            for(int i=0; i<10; ++i)
                    printf("%d ", a[i]);
            printf("\n");
            sort(a, 10);
            for(int i=0; i<10; ++i)
                    printf("%d ", a[i]);
            printf("\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    我们可以使用 gcc *.c -g -o app 生成可调试的程序。

    然后使用 gdb app 可以开始调试

    • l 列出代码(包含main函数的程序)的10行

    • l 文件名:行数 显示文件名行数开始的十行代码

    • l 文件名:函数名 显示文件名里的函数

    • Enter回车 可以执行前一条命令

    • start 单步执行,运行程序,n 执行下一行

    • b 行号 设置断点,r 运行程序在断点处停止,断点可以加判断比如 b 行号 if i==5

    • p 变量名 可以打印变量,display 变量名 可以追踪变量

    • s 执行下一行,且可进入函数体内部,finish 可以结束当前函数,返回到函数调用点,这时需要断点代码执行完毕或者删除断点,删除断点可以先使用 info b 查看断点编号,然后d 断点编号删除该断点

    • set var i=5 可以设置变量 i 为5

    • undisplay 编号 可以取消追踪变量,可以使用 info display 查找变量编号。

    • ``

  • 相关阅读:
    Bigemap如何添加谷歌历史影像
    ​LeetCode解法汇总2525. 根据规则将箱子分类
    P8824 [传智杯 #3 初赛] 终端 —Map根据value排序实例
    Android修改分区格式为F2FS
    常见项目管理中npm包操作总结
    mysql数据库
    堆排序(大根堆与小根堆)
    修复dinput8.dll丢失的简单方法,解决dinput8.dll丢失
    RabbitMQ二、RabbitMQ的六种模式
    Web安全专业学习路线
  • 原文地址:https://blog.csdn.net/weixin_44491423/article/details/126858315