Linux-Chap5-C语言编程

本文详细介绍了Linux中C语言编程的相关知识,包括C语言的特性、如何编写和执行C程序、程序构成、变量与常量、类型转换、数组、控制结构、函数、指针等。还探讨了C语言编译器gcc的使用、makefile的作用以及如何管理库文件。此外,文章提到了调试器gdb的使用和Linux系统函数库及系统调用的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux中的C语言程序设计

C语言:是Linux中最常用的系统编程语言之一。功能极其强大的程序设计语言。包含直接操作内存和硬件的底层计算语句,也包含高级语言所具有的数据结构、控制语句、函数调用等功能。是一门编译语言,而不是脚本语言。可使用Linux函数库和系统调用。

如何编写和执行C语言程序:使用任何文本编辑器编写C语言程序源文件.c。使用C语言编译器gcc编译源文件,生成独立可执行文件。在遇到错误时,编译器会给出错误信息停止编译,不会生成目标文件。

gcc编译格式:
    gcc 源文件名 #生成的目标文件名为默认文件名a.out
    gcc 源文件名 -o 目标文件名
    gcc -o 目标文件名 源文件名

C语言程序构成:预处理器指令+程序主体。

       预处理器指令:在文件开头以#开始的指令,不会被C语言编译器采用。常用来引用C语言头文件.h或设定宏。

#include <stdio.h>
#define PI 3.14

       程序主体:变量声明、函数声明、函数定义、main主程序。

变量和常数:变量名必须以字母或_开头,其余部分可以包含字母、数字和_。每个变量必须声明类型后才能使用。变量在使用前必须初始化。

基本类型:int, char, float, double
派生类型:数组和指针
用户定义类型:结构,枚举,typedef

常量:
    普通常量:const 类型 常量名=常量值
    符号常量:#define 常量名 常量值

类型转换:

       自动转换:数值类型自动转换,将一个较低类型变换为较高类型。表达式的值自动转换成赋值左边的类型,有可能造成信息丢失。char和short类型会自动变换为int类型。

float pi = 22/7.0;

       隐式变换:常量使用后缀:L,F。强制类型转换:(类型)表达式

数组:数组声明:type array[n]={val1,val2,...,valn}; 数组下标从0开始,如长度为n,则最后一个数组下标为n-1。C编译器不做数组边界检查,可以访问未声明的数组下标项,但很危险。数组的实质是指向一组连续内存空间的指针。数组名表示的是数组中第一个元素的内存位置。字符串就是一个char类型的数组,最后一个元素为NULL(\0)。

控制结构语句:与awk和perl的控制结构语句格式相似。

switch语句
switch (exp) {
    case val1: statements1; break;
    case val2: statements2; break;
    ...
    default: statementsn;
}

do-while语句
do {
    statements;
} while (exp)

函数:

       函数声明:

void function(void);
type function(type1 var1, type2 var2, ...);

       函数定义:定义局部变量和函数体。

       函数调用:

function()
var=function(exp1,exp2,...)
return 结束函数并返回值

       函数参数传递:传值:实参的值赋给独立的形参。传址:实参的地址赋给形参的指针。

指针:单个内存位置的数值地址。指针变量:存储指针的变量。用途:直接操作内存地址、函数传址调用。

声明:type *p;
内存地址:&p
存储数据:*p
指针变量赋值:p=&a; *p=val;

指针运算:指针类型的大小不同于其指向的数据类型。指针的大小是固定的。指针之间的算术运算按照指针指向的存储空间大小来进行。对于未初始化的指针进行访问和操作都是危险的。任何类型的指针都可以指向NULL,void类型的指针可以指向任何类型。

int *p; //sizeof(p)=8; sizeof(*p)=4

int *p=&a;
p+n 等价于 p+n*sizeof(p)
*(array+n)等价于array[n]
*(p++) //把指针往后挪了一个步长,然后取值
*(p)++ //把p值取出,把值+1
&array[m]-&array[n]等于m-n //返回的不是地址差值,是差几个单元

指针与字符数组:

char st1[] = {'H','e','l','l','o','\0'}; //最后一位是NULL字符。
char st2[] = "Hello"; //给字符数组赋值字符串时会自动添加NULL
char *st3 = "Hello"; //给char指针赋值字符串时会自动添加NULL
可以通过printf的%s来输出st1,st2,st3
st1和st2是常量,不能进行赋值操作
st3可以进行指针运算,可以指向别的地址,也可以指向其他数组
C语言中不能直接比较两个字符串(st1 == st2 是非法的)

C语言命令行参数:在main函数定义中给出形参。在执行时,命令行参数存储在字符串数组argv中,字符数组的指针。参数个数存储在整型变量argc中。形参名字可以自定义。

int main(int argc, char *argv[ ])

C语言编译器和相关工具

C语言程序开发工具:编译器gcc,编译器更新make,生成库ar,调试工具gdb,版本控制工具。

gcc编译器:gcc命令包含预处理.i、编译.s、汇编(不可直接运行)和连接(加上头文件/库文件)4个步骤,最终生成可执行代码。gcc接受多种文件类型并依据用户指定的选项做相应处理。

格式:gcc [options] filenames
文件类型对应程序
.cC语言源程序
.i经过预处理的源程序
.a编译后的库文件
.s汇编语言源程序
.h预处理文件(头文件)
.o目标文件
选项含义(得到4个步骤的中间结果)
-o file将目标文件保存到file
-E只做预处理,生成经过预处理的.i文件
-S只做预处理和编译阶段,生成汇编代码.s文件
-c不做连接步骤,生成.o目标文件
-I dir在头文件搜索路径中加入dir
-L dir在库文件搜索路径中加入dir
-l lib连接库文件lib
-O[0-3]根据指定级别进行编译优化

gcc编译过程:①预处理(符号常量换成设定的值):预处理器将所有预处理指令替换为编译器可以理解的形式。②编译:编译器将C源代码转换为汇编语言代码。③汇编:汇编器(as)将汇编代码转换成目标代码,但这些代码并不完整。④链接:链接器(ld)将目标代码和系统函数库文件和其他目标文件链接起来,生成单一可执行文件。

C语言程序源文件:

头文件.h

系统函数声明等stdio.h

用户自定义函数声明

源文件.c

main函数定义

头文件中声明的函数的定义部分

库文件.a .so

系统函数库文件,包括静态链接库.a和动态链接库.o

gcc source -o target -l libfile

库文件必须以lib开头,执行-l引用时可以省略lib和.a .so

编译多个源文件:

gcc -c file1.c file2.c ...      //编译多个.c文件生成相应的.o目标文件
gcc -o file file1.o file2.o ... //链接.o文件生成可执行目标文件file

保留目标文件.o的意义:假设c文件的编译依赖于a,b文件,当a文件有更新时,只需重新编译a文件,而不需要重新编译未修改的b文件,只需要在最后阶段链接已有的b.o文件即可。可以合并一组.o文件,构成一个库,供将来使用。

如何发现某个文件已被更新?可以比较.o文件和其源文件.c的修改时间。如果.c文件新于.o文件,则需要重新编译该文件,并重新编译所有依赖于该文件的文件。

makefile:指定每个文件的依赖关系,并在依赖文件有更新时执行相应的操作。当一个源文件被修改时,所有依赖于该源文件的文件都需要进行重新编译。使用make命令来检查依赖文件的更新状态并执行makefile中设定的操作。

makefile文件格式:
包含一组规则,每个规则有如下形式:
target: dependency_list //目标文件以及其依赖于那些文件
    command_list  //注意前面是制表符,如更新,重新编译
//当dependency_list中的任何文件修改时间晚于target,则执行command_list

当目标文件与依赖项的基名称相同,则不需要command_list。

(例如:quit.o: quit.h quit.c 不需要指定gcc -c quit.c)

可以在依赖项中省略同名源文件本身

(例如:quit.o: quit.h quit.c 中可以省略quit.c)

make命令支持用-f选项指定makefile文件,支持使用target作为参数执行指定规则。(冒号之前的)

cat makefile
#Makefile
interest: interest.o arg_check.o quit.o
        gcc -o interest interest.o arg_check.o quit.o -lm -g
interest.o: interest.c arg_check.h quit.h
        gcc -c interest.c -g
quit.o: quit.h quit.c
        gcc -c quit.c -g
arg_check.o: arg_check.h arg_check.c
        gcc -c arg_check.c -g

//判断冒号之后的list文件有无更新 有更新则执行第二行的gcc命令
//如果.o后面的list更新 则重新编译list里的.c文件

//修改interest.c后
make
gcc -c interest.c -g //interest.o更新 所以interest更新
gcc -o interest interest.o arg_check.o quit.o -lm -g

//修改arg_check.c后
make
gcc -c arg_check.c -g //quit.o更新 所以interest更新
gcc -o interest interest.o arg_check.o quit.o -lm -g

makefile中的变量:makefile支持声明和使用变量。变量分为环境变量、预定义变量和自动变量。变量的声明:在makefile开头以var=value来定义。变量的使用:在规则中用$(var)来使用。make命令会将变量替换为其当前值。(相当于shell命令,字符串赋给一个变量,如CC=gcc)

预定义变量和自动变量:

变量名称含义
CCC编译器名称,默认为cc
AS汇编器名称,默认为as
RM文件删除命令名称,默认为rm -f
AR库文件管理命令名称,默认为ar
$*不包含扩展名的目标文件名称
$+所有依赖文件,以空格分开,可能出现重复
$<第一个依赖文件名称
$?所有时间戳比target文件晚的依赖文件
$@目标文件的完整名称
$^所有不重复的依赖文件

ar:生成静态库命令:将.o文件打包成一个静态库,便于管理。类似于tar命令。静态库文件命名方式:lib+name+.a

ar [option] libname.a file_list

-r  添加文件到库中,或替换已有文件
-q  在库中追加文件
-x   从库中提取某文件
-d  删除库中的某文件
-t   显示库内容
-v  显示操作内容

ar -rv libit.a quit.o arg_check.o

生成动态共享库:动态共享库是在程序执行时去链接的库。共享库命名规则:lib+name+.so

生成共享库:
    gcc -shared libname.so file1.o file2.o ...
使用共享库:
    编译时声明:gcc -o main main.c -lname -Ldir //告诉动态链接库的位置dir

程序运行时动态共享库的路径指定:①将.so文件复制到/lib或/usr/lib目录下(需要root权限)。②设置环境变量LD_LIBRARY_PATH

静态库与共享库:①使用-l参数链接库文件。②对于符合命名规则的库文件,可以直接使用-lname。③默认库文件路径:/lib和/usr/lib。④gcc使用-L选项指定库文件目录。⑤-l参数会优先选择共享库(动态),然后是静态库。

gcc interest.c -lit -L. -lm 
//-L. 当前目录
//-lm 系统库文件
静态库动态库
.a.so
在链接时插入目标文件在运行时调用
运行时不需要库文件运行时需要指定库文件路径
可执行文件占用空间大可执行文件占用空间小

在makefile中管理库文件:在makefile中使用ar_name(file_name)的方式来访问库文件。执行make命令时,会调用ar命令对库中的文件进行更新。

CC=gcc
interest: interest.o libit.a(quit.o) libit.a(arg_check.o)
        $(CC) -o $@ $< libit.a -lm

interest.o: quit.h arg_check.h

libit.a(quit.o): quit.h

libit.a(arg_check.o): arg_check.h

//如果源文件有更新,则更新在库文件中

调试器:调试器时控制另一个程序运行的程序。可以捕获任意时刻的程序状态;可以指定断点,暂停程序,观察变量的状态;可以修改程序的错误,并重新运行程序。使用gcc -g选项编译程序才能使用调试器。

gdb调试器:普及度最高的调试器。支持C,C++,Fortran,Java。可以在所有Linux系统上运行。

使用模式:①调试模式:运行程序,允许监控每条语句的执行。②附加模式:将自身附加到任意正在运行的进程,获取该程序所用的内存信息。③事后模式:当程序崩溃时,可以查看被转储的core文件,以判断崩溃原因。

gdb调试器命令:

gdb [options] [可执行程序[core文件|进程ID]]
-c corefile 检查指定的core文件
-q 禁止显示介绍信息

使用方法:
    用gcc -o main main.c -g 编译程序
    用gdb main 启动调试器
    在提示符(gdb)下输入调试命令
    用quit退出gdb
命令含义
list显示10行源代码 //list5 以第五行为中心的上下10行
list m,n显示m到n行源代码
run执行程序,直到下一个断点
cont继续执行
next执行下一条语句,函数体看成一条语句
step执行下一条语句,进入函数体(函数体第一条语句之后停)
break n在第n行设置断点
break function在调用函数function的行设置断点
break n functioncondition为真时,在第n行设定一个断点
watch x设定对变量或表达式x的观察,在每次x值变化时暂停
whatis x显示变量或表达式x的类型
display/print x显示变量或表达式x的值
quit退出gdb调试环境

Linux的C程序系统函数库

数学函数:头文件:#include <math.h> 函数库:libm.a/so ==> lm

pow 乘方函数
    double pow(double x,double y)
sqrt 开平方根函数
    double sqrt(double x)

随机数函数:头文件:#include <stdlib.h>

rand:产生随机数
    int rand(void)
    返回值:0~RAND_MAX之间的随机数
    通常使用 1+(int)(n*rand()/(RAND_MAX+1.0)) 来产生1~n之间的随机数
    需要先设置随机数种子

srand:设置随机数种子
    void srand(unsigned int seed)
    通常使用time函数返回当前时间作为种子(数值大且每次都不同)

#include<stdlib.h>
#include<stdio.h>

int main()
{
	int i,j,k;
	printf("The seed is %d.\n",(int)time(0));
	srand((int)time(0));
	for (i=0;i<10;i++)
	{
		j=1+(int)(10.0*rand()/(RAND_MAX+1.0));//1-10之间的随机数
		k=rand()%50+1;//1-50之间的随机数
		printf("%d  %d  ", j,k);
	}
	printf("\n");
}

字符函数:头文件:#include <ctype.h>

函数功能
bool isalnum(char c)是否为字母或数字
bool isalpha(char c)是否为字母
bool isdigit(char c)是否为数字
bool islower(char c)是否为小写字母
bool isupper(char c)是否为大写字母
bool isspace(char c)是否为空格
bool ispunct(char c)是否为特殊符号
bool isxdigit(char c)是否为十六进制数字

系统时间和日期函数:#include <time.h>

struct tm 
{
	int tm_sec;
	int tm_min;
	int tm_hour;
	int tm_mday;
	int tm_mon;
	int tm_year;
	int tm_wday;
	int tm_yday;
	int tm_isdst;
}

time:返回当前时间
    time_t time(time_t *t) //time_t可与整数相互转换
    返回值存储在t指针指向的地址
    成功则返回1970年1月1日开始的秒数,失败则返回-1

localtime:取得当前时间和日期,以本地格式返回
    struct tm * localtime(const time_t *timep)
    输入time_t类型指针,输出tm结构类型指针

#include<time.h>
#include<stdio.h>

int main()
{
	time_t timep;
	char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
	struct tm *p;
	time(&timep);//返回值存入timep变量里
	printf("The current time by time function is %d \n", timep);
	printf("The current time in GMT is %s \n",asctime(gmtime(&timep)));
	//gmt=标准时间格式 asc 用通常方式显示
	p=localtime(&timep);
	printf("The current local time is %d/%d/%d ",1900+p->tm_year,1+p->tm_mon,p->tm_mday);//返回值是2位 前面加1900
	printf("%s %d:%d:%d\n", wday[p->tm_wday],p->tm_hour,p->tm_min,p->tm_sec);
	return 0;
}

环境控制函数:头文件:#include <stdlib.h>

getenv:获取环境变量值
    char* getenv(const char *name)
    输入环境变量名称,输出环境变量值,若环境变量不存在则返回NULL

setenv:设置环境变量值
    int setenv(const char *name, const char *value, int overwrite)
    name为环境变量名,value为设置值,overwrite=1会覆盖原有值
    成功返回0,不成功返回-1

内存分配函数:头文件:#include <stdlib.h>

malloc:分配一个动态内存空间
    void *malloc(size_t size)
    分配一段大小为size的动态内存空间
    成功则返回指向该内存地址的指针,失败返回NULL

calloc:分配多个动态内存空间
    void *calloc(int num, size_t size)
    分配num个连续的大小为size的动态内存空间,并初始化为0
    成功则返回指向该内存地址的指针,失败返回NULL

free:释放动态内存空间
    void free(void *p)
    释放p指针指向的内存空间,只能释放已分配的动态内存空间

内存操作函数:

memset:初始化指定内存空间
    void *memset(void *buffer, int c, int count)
    buffer是指定内存空间的指针,c为初始化内容,count是初始化字节数
    将buffer所指内存区域的前count个字节设置为c

memcpy:复制内存空间
    void *memcpy(void *dest, void *src, int count)
    dest为目标内存区,src为源内存区,count为要复制的字节数
    将src内存区中的前count字节复制到dest内存区

memcmp:比较两个内存空间的字符
    int memcmp(void *buf1, void *buf2, int count)
    buf1,buf2为两个内存区,count为要比较的字节数
    比较内存区域的前count个字节,根据ASCII码表顺序比较
    buf1<buf2, return <0; buf1=buf2, return=0; buf1>buf2, return>0

system函数:执行shell命令。头文件:#include <stdlib.h>

int system(const char *string)
    输入字符串string将作为shell命令由/bin/sh执行
    执行成功返回shell命令的返回值

Linux的C程序系统调用

C语言系统调用:系统调用是一种例程,可以由C程序调用来访问系统资源。(文件I/O、分配内存、进程创建)。系统调用的使用方式类似于函数调用。系统调用的很多命令 与shell命令同名。执行时需要切换到内核模式,执行完毕之后切回用户模式。内核代理操作,用户无法管理内部操作。

系统调用与库函数的区别:可以使用 man 2+系统调用名字 命令查看系统调用的文档。可以使用 man 3 命令查看库函数的文档。

系统调用库函数
系统资源低层接口高层应用接口,对低层封装
操作精细程度高操作精细程度低
有时效率较低效率较高
执行模式需要切换,开销大开销较低

错误处理:系统调用在发生错误时返回-1(int)。错误代码——全局变量errno用一个正整数记录错误的类型信息。

通过perror命令显示与errno相关联的错误信息
    #include <sys/errno.h>
    void perror(const char *str)
    输出str: errno错误信息 //str内容可以自定义
符号常量errno消息
EPERM1Operation not permitted
ENOENT2No such file or directory
ESRCH3No such process
EINTR4Interrupted system call
EIO5I/O error
EACCES13Permission denied
EEXIST17File exists
ENOTDDIR20Not a directory
EISDIR21Is a directory
ENOSPC28No space left on device
ESPIPE29Illegal seek
EROFS30Read only file system

open:打开和创建文件。头文件:#include <fcntl.h>

int open(const char* path, int oflag, int smode)
输入参数:
    path 文件路径
    oflag 打开模式 O_RDONLY, O_WRONLY, O_RDWR, 中间用|分隔
    smode 创建文件时设定权限,可用8进制数表示,前面加0前缀
返回值:一个标识符fd
    文件说明符,通常从3开始 //0-标准输入流 1-标准输出流 2-标准错误流
    错误时返回-1 

close:关闭文件。close会释放指定的文件说明符,供下一个open使用(释放文件资源)。当由多个open打开同一个文件时,只有所有指向该文件的说明符都释放后文件才会关闭。可以使用close关闭标准输入(0),标准输出(1),标准错误(2)。

int close(int fd)
输入参数:fd 文件说明符(open的返回值)
返回值:成功时返回0

read:读取文件。

ssize_t read(int fd, void *buf, size_t nbyte)
输入参数:
    fd 文件说明符
    buf 指向通用缓冲区的指针 //读的内容放在缓冲区内(固定byte大小)
    nbyte 缓冲区本身的大小,也是读取的字符数
返回值:
    在遇到EOF之前,返回nbyte
    遇到EOF时,返回所读字符数,下一次执行read时返回0 //可用于判断是否读完

write:写入文件。把缓冲区里的东西写入文件流。

ssize_t write(int fd, const void *buf, size_t nbyte)
输入参数:每次调用都将buf中的nbyte个字节写入文件说明符fd中
返回值:
    返回所写字符数 //实际写了多少字符
    在磁盘已满或文件大小超出限制时返回-1
缓冲区大小:
    写入设备
    内核缓冲区大小
#include<fcntl.h> 
//文件相关
#include<sys/stat.h> 
//系统状态
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#define BUFSIZE 4096 
//缓冲区大小
#define OFLAGS O_WRONLY | O_CREAT | O_TRUNC
#define SMODES S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH 
//user的r和w权限 group的r和w权限 other的r权限
//相当于权限664
int main()
{
	int fd1,fd2;
	int n;
	char buf[BUFSIZE];
	
	if ((fd1=open("/etc/passwd", O_RDONLY))==-1)//相当于输入流
	{	
		perror("open1");
		exit(1);
	}
	if ((fd2=open("passwd.bak", OFLAGS, SMODES))==-1)//可以理解为输出流 可创建一个文件
	{
		perror("open2");
		exit(2);
	}
	while ((n=read(fd1,buf,BUFSIZE)) > 0)//从fd1中读出东西存在buff里 一次读4096 跳出循环 读=0或<0
	
	{
		if (n!=write(fd2,buf,n))//写入fd2 写入n个大小 读多少写多少
		//正常情况write返回值=n 错误则返回错误并退出
		{
			perror("write");
			exit(3);
		}
	}
	close(fd1);
	close(fd2);
	exit(0);
}

lseek:移动文件指针到指定位置。

off_t lseek(int fd, off_t offset, int whence)
输入参数:
    whence 指针基准位置:
        SEEK_SET 文件开头 开头+offset
        SEEK_END 文件结尾 结尾+offset(offset为负)
        SEEK_CUR 当前位置 offset可正可负
    offset 相对于基准位置的偏移量,正整数或者负整数
返回值:返回指针到文件开头的距离
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include"quit.h"
#include"arg_check.h"

int main(int argc, char **argv)
{
	int size, fd;
	char buf;
	
	arg_check(2,argc,"Not enough arguments!\n",1);//正常给两个参数
	
	if ((fd=open(argv[1], O_RDONLY))==-1)
		quit("open",1);
	
	lseek(fd,1,SEEK_END);//指针移动到文件结尾再往后挪一个
	while (lseek(fd,-2,SEEK_CUR) >= 0)//当前往前偏2个==读文件最后一个字符
	{
		if (read(fd,&buf,1)!=1)//正常 = 1 指针往后移动一个
			quit("read",2);
		if (write(STDOUT_FILENO, &buf, 1)!=1)//stdout为标准输出的符号常量 把该字符写到buf里
			quit("write",3);
	}//循环结束即是把文件倒着输出一遍
    //read和write指针都会动,但是流不同
	buf='\n';
	write(STDOUT_FILENO, &buf, 1);
	close(fd);
	exit(0);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值