易懂的在Linux中进行C语言编程记录

Linux下的C编程调试

在学习使用Ubuntu系统的过程中,如何使用Ubuntu进行编程是一件十分基础的事,下面我最开始使用Ubuntu进行C编程的一段过程。

环境的准备

首先输入sudo apt install -y build-essential,作用是提供编译程序必须软件包的列表信息。
再输入sudo apt install openjdk-11-jdk。(Ubuntu20.04系统下)

程序编写及对gcc的初步了解

GCC是以GPL许可证所发行的自由软件,也是GNU计划的关键部分。GCC的初衷是为GNU操作系统专门编写一款编译器,现已被大多数类Unix操作系统(如Linux、BSD、MacOS X等)采纳为标准的编译器,甚至在微软的Windows上也可以使用GCC。GCC支持多种计算机体系结构芯片,如x86、ARM、MIPS等,并已被移植到其他多种硬件平台 。GCC原名为GNU C语言编译器(GNU C Compiler),只能处理C语言。但其很快扩展,变得可处理C++,后来又扩展为能够支持更多编程语言,如Fortran、Pascal、Objective -C、Java、Ada、Go以及各类处理器架构上的汇编语言等,所以改名GNU编译器套件(GNU Compiler Collection)。

首先输入touch hello.c创建一个名为hello.c的文件。

然后我们输入vim hello进入vim的编辑页面对hello.c进行编辑。
我们编写一个基础的c语言程序。
请添加图片描述
然后保存。
这时候我们输入ll,可以看到这个文件是不可执行的。
请添加图片描述
接下来我们尝试使用gcc对这段代码进行编译。
先输入gcc -E hello.c -o hello.i看看,我们会发现生成了一个hello.i文件。
用vim进行查看我们会看到
请添加图片描述
这是gcc对我们的程序进行的预处理操作。

  • -E 指令使gcc执行预处理,预处理时对#类指令进行处理(包含头文件、替换宏常量和宏代码段等操作)。
    之后我们再尝试一下这个命令。
    gcc -S hello.c -o hello.s
    这样我们就会得到一个hello.s文件。
    使用vim查看
    请添加图片描述
  • 执行-S(大写)指令将*.i文件中源码转化为汇编代码*.s文件,打开会发现c源码已经被编译器转化为汇编代码。
    再输入gcc -c hello.c -o hello.o
    同样我们会得到一个hello.o文件。
    打开后我们会发现请添加图片描述
    出现了一堆乱码,这是因为执行-c(小写)指令将*.s文件中的汇编源码转化未机器能执行的二进制机器码,生成文件*.o。
    最后输入gcc hello.o -o hello,终于得到了我们希望的程序。
    输入./hello运行程序。
    请添加图片描述
    这样我们就输出了我们想要的结果:Hello World!

同时我还试了试修改这个程序,改为用hello函数调用输出。
因为内容差异并不大,并且与本博客后面的内容有关,不再过多赘述和解释。过程如下:

$ touch hello.c
$ touch makefile
$ vim hello.c
#include <stdio.h>
void hello()
{
        printf("hello\n");
}

int main()
{
         hello();
        return 0;
}
$ vim makefile
main:hello.c
        gcc -E hello.c -o hello.i
        gcc -S hello.i -o hello.s
        gcc -c hello.s -o hello.o
        gcc hello.o -o hello

clean:
        rm *.i *.s *.o
$ make
$ ./hello

请添加图片描述

总结:

选 项 名作 用
-c通知gcc取消连接步骤,即编译源码并在最后生成目标文件
-E不经过编译预处理程序的输出而输送至标准输出
-S要求编译程序生成来自源代码的汇编程序输出
.h预处理文件(标头文件)

使用gdb调试函数调用

我们已经了解了如何使用gcc对程序进行编译,接下来我们将学着使用gdb对一个有可能有bug的文件进行调试。
现在我们创建一个new文件。
touch new.c
编辑如下代码

  • 这里我借鉴了一段代码,来源在文章最后,内容因为有bug所以有修改,bug内容接下来说。
#include <stdio.h>
#include <unistd.h>
extern void test();
extern long* go_point;
void test_1()
{
    printf("test_1() : %p\n", test_1);
}

void test_2()
{
    printf("test_2() : %p\n", test_2);
}

void test_3()
{
    printf("test_3() : %p\n", test_3);
}
int main(int argc, char *argv[])
{
    typedef void(TFunc)();
    TFunc* fa[] = {test_1, test_2, test_3};
    int i = 0;

    printf("main() : begin...\n");

    for(i=0; i<argc; i++)   //argc代表命令行参数的个数
    {
        printf("argv[%d] = %s\n", i, argv[i]);
    }

    for(i=0; i<100; i++)
    {
        fa[i%3]();
        sleep(argc > 1);    // 如果argc大于1,则执行睡眠函数
    }

    printf("go_point = %p\n",go_point);

    test();

    printf("main() : end...\n");

    return 0;
}

请添加图片描述
再新建一个test文件
touch test.c

#include<stdio.h>
long* go_point;

void test()
{
        *go_point = (long)"Eilo";
        return;
}

请添加图片描述

之后我们运行gcc编译这两段代码。
gcc new.c test.c -o test.out

这时候我遇到了一个bug
请添加图片描述

当时我的test代码内容是这样的:

#include<stdio.h>
int* go_point;
void test()
{
go_point = (int)"Eilo";
return0;
}

查找资料后我得知,int 在32bit和64bit系统中都占4个字节,但是,32bit指针占4个字节,64bit指针占8个字节,因此就有问题了。
解决方法是将定义的int* go_point改为long* go_point,使其的字节变为8。
这时候我们的编译就能成功了。
这时候我们运行这个程序。
./test.out
后发现会出现下面这个现象。

请添加图片描述
请添加图片描述
这是在意料之中的,毕竟在test.c程序中,我们对0地址进行写内容了。

同时这也说明我们的程序出现了一个bug,要怎么找到bug并解决呢。
这时候我们可以使用gbd对其进行调试。
在开启gdb调试之前,需要在编译源程序的时候加上-g选项,并将程序的崩溃信息转储的core文件。
gcc -g new.c test.c -o test.out //重新编译加上调试信息
ulimit -c unlimited //让程序在崩溃时产生core文件
./test.out //重新运行看看是否产生core文件

请添加图片描述
core dumped的意思是核心已经转储,
但是奇怪的是,在我的当前目录下并没有这个core文件,于是我去搜索core不能生成的原因。根据资料说apport 服务会阻止生成core文件,所以我们可以输入sudo service apport stop关掉apport 服务。
然后重新运行test.out文件。
请添加图片描述
我们就得到了core文件了。
现在就是出现了段错误,我们需要使用GDB的断点调试来找出问题所在,可以使用软件断点调试,硬件断点调试两种方法调试,这里只介绍软件断点调试,较为详细的可以从参考博客中了解。
接下来按顺序输入以下代码

ls//检查当前目录下是否有所需文件
gdb test.out//使用gdb打开test.out
set args Eilo
start//start命令run命令的区别是start后,程序已启动就立马停止,GDB会自动在程序开始出打上一个断点。而run命令执行后程序会直接跑起来。

请添加图片描述
然后我们使用命令:break new.c:36 // 这个命令在new.c的36行打断点
使用命令:info breakpoints //查看我们的断点数量如下图所示:请添加图片描述
可是很神奇的是,我们明明是在36行打的断点,在断点列表里却显示的是38行。然后我们使用disable 1来暂时禁用这个断点。
再输入break new.c:35来在35行打一个断点。
请添加图片描述
这样我们会看到确实在35行打了一个断点。

这是为什么呢?

我查找资料也没有找到具体原因,但由上面的两个不同位置断点的设置,我推断在gdb里面如果断点设置在空白行或者仅有{或者}的地方,就会自动把断点设置到下一行,直到不满足上述条件。

输入continue命令继续执行程序,但是这时候我又遇见了一个问题
请添加图片描述
程序并没有运行。

这又是为什么呢?

原来是我在第一次设置断点的时候,中途退出了gdb,请添加图片描述
这样我当时的进程就被终止了,所以我必须再一次start它。
请添加图片描述
后continue
请添加图片描述
果不其然,程序在35行终止了。
请添加图片描述

  • 这很正常因为我们在new.c的35行打了断点。又因为35行是for循环的结尾处,所以此时才相当于执行了一次for循环但是还没完全循环一次,输入print i此时i=0。

  • 想要单步执行,就继续输入next,则程序就会一步一步执行。我们输入了很多个next,发现这个for循环一直可以正常执行,所以这个for循环肯定是没有问题的。那么我们就不必在for循环内部执行了,可以直接将i设置为100,执行完for循环。
    请添加图片描述

输入命令set var i=100然后输入两次next,就跳出了for循环。
请添加图片描述

  • 现在已经确定for循环没有产生错误,那么段错误就是在for循环之后。for循环之后只有两个printf语句和一个test函数调用。现在我们怀疑是test函数内部出现了段错误。我们在test函数调用所对应的行(40行)打一个断点:
    break new.c:40
  • 然后输入continue执行,执行到test函数时停下来了,然后使用jump new.c:44程序直接正常退出。
  • 但是这里又出现了意料之外的错误:
  • 请添加图片描述
  • 这里程序已经运行到bug出现的地方了,我尝试跳出这个bug,但是总是显示要跳出的行数不在test内,当时我以为是我行数数错了,于是又尝试了其他行数,但依旧显示不在test内。
  • 之后我多次重新start这个程序,在经过一番努力后,我意识到我这时所在的程序并不是new.c而是test.c,而这个文件只有几行,当然不会执行成功。
    意识到这个问题过后,我又一次重新上述步骤。并且在出现bug的时候输入jump test.c:7来跳出这个bug。终于得到了需要的结果。
  • 显示程序运行正常,由此可见,bug出现的地方确实是test函数的调用。
  • 这样我们便能够用gcc的断点在不改变原始代码的前提下找到bug出现的地方。
    请添加图片描述

使用makefile对gcc调试过程进行管理

关于makefile:

make是一个命令工具,它解释Makefile 中的指令。在Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。

我们使用先创建一个makefile文件
然后将我们在上面gcc调试过程的命令按下图的格式输入到里面:

main:hello.c
        gcc -E hello.c -o hello.i
        gcc -S hello.i -o hello.s
        gcc -c hello.s -o hello.o
        gcc hello.o -o hello

clean:
        rm  *.i *.s *.o 
  • 这里要注意一下makefile的使用

makefile格式:

arget : dependency_files ##dependency_files是依赖的文件
	command #注意是一个TAB

为了让程序可以方便地除去我们不需要的多余文件,我在makefile中也写了一个clean用来清除它们。
然后我们输入make
请添加图片描述

这样我们就能快速地运行并得到我们所希望的hello文件了。
输入./hello运行hello程序。
然后我们输入make clean再输入ls我们可以发现多余的文件已经被删除了。
这样,我就完成了使用makefile对gcc进程的管理。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是关于Linux系统的网络聊天室采用C语言编程的课程设计的系统总体设计和需求分析的一些思路和建议: 一、系统总体设计: 1. 架构设计: 网络聊天室系统的架构设计应该采用客户端-服务器(Client-Server)模型,即多个客户端通过网络连接到服务器,服务器负责接收和转发客户端之间的消息。在该架构下,服务器和客户端之间应该要遵循一定的通信协议。 2. 通信协议设计: 定义一套简单易懂的通信协议是网络聊天室系统设计的关键之一。协议应该规定消息的格式、编码方式、命令字等,以确保客户端和服务器之间的通信可以互相理解。在协议设计应该要考虑到安全性、可靠性和可扩展性等方面的因素。 3. 功能设计: 网络聊天室系统的主要功能包括:用户登录、用户注册、用户列表显示、聊天室创建、聊天室列表显示、聊天室加入、聊天室退出、消息发送等。在功能设计应该要考虑到用户体验、易用性和系统性能等方面的因素。 二、需求分析: 1. 用户需求: 用户需求是设计网络聊天室系统的出发点,应该要从用户的角度出发,考虑到用户的需求和使用习惯,以提高系统的易用性和用户满意度。用户需求可以包括:用户登录、用户注册、用户列表显示、聊天室创建、聊天室列表显示、聊天室加入、聊天室退出、消息发送等方面。 2. 系统需求: 系统需求是设计网络聊天室系统的另一个重要考虑因素,应该要从技术实现的角度出发,考虑到系统的可靠性、可扩展性、安全性和性能等方面。系统需求可以包括:架构设计、通信协议设计、功能设计、安全性设计、性能优化等方面。 以上是对于Linux系统的网络聊天室采用C语言编程的课程设计的系统总体设计和需求分析的一些思路和建议,希望对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值