Linux小知识---C语言中的一些S操作

前言

用C语言开发也十来年了,不过对于C语言中,还是有很多不熟悉的不常用的做法,一般在阅读其他开源代码的时候,会偶尔遇到一些奇怪的写法,或者不常见的函数。

在这里插入图片描述

#表达式

用来将宏参数转换为字符串,也就是宏参数的开头和末尾添加引号,

#define STR(a)  #a
printf("%s\n",STR(abc));
printf("%s\n",STR("abc"));

输出结果为

abc
"abc"

简单来说,就是我输出我自己。
在这里插入图片描述

##的用法

##成为连接符,用来将宏参数和其他的字符串连接起来。

#define DEBUG(a) debug##a
int DEBUG(1)=5;

等价于定义了一个变量。

int debug1=5;

也不知道发明这个有啥用

在这里插入图片描述

断言

在 C 语言中,断言被定义为宏的形式(assert(expression)),而不是函数,其原型定义在<assert.h>文件中。其中,assert 将通过检查表达式 expression 的值来决定是否需要终止执行程序。也就是说,如果表达式 expression 的值为假(即为 0),那么它将首先向标准错误流 stderr 打印一条出错信息,然后再通过调用 abort 函数终止程序运行;否则,assert 无任何作用。
一个例子,来判断入参合法性

void *MemCopy(void *dest, const void *src, size_t len)
{
	assert(dest != NULL && src !=NULL);
	char *tmp_dest = (char *)dest;
	char *tmp_src = (char *)src;
	while(len --)
	*tmp_dest ++ = *tmp_src ++;
	return dest;
}

不过这个只是在开发阶段有用,正式版本一般不会使用这个

在这里插入图片描述

逗号表达式

最近在代码里看到这样一句话,
在这里插入图片描述
才开始接触到了这个表达式。
逗号表达式是C语言中的逗号运算符,优先级别最低,它将两个及其以上的式子连接起来,从左往右逐个计算表达式,整个表达式的值为最后一个表达式的值。”

所以上图中其实就是从左向右执行,返回最后一个值,这里最后一个值作为了判断条件。
也不知道是不是为了炫技。
在这里插入图片描述

函数atexit()

#include<stdlib.h>
int atexit(void(*func)(void));

返回:若成功则为0,若出错则为非0

按照ANSIC的规定,一个进程可以登记多至32个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序(exithandler),并用atexit函数来登记这些函数。
其中,atexit的参数是一个函数地址,当调用此函数时无需向它传送任何参数,也不期望它返回一个值。exit以登记这些函数的相反顺序调用它们。同一函数如若登记多次,则也被调用多次。

上面是unix环境高级编程的解释:

个人理解:如果进程正常结束,不会立刻结束,会做一些‘善后工作’(比如刷新关闭打开的流缓存,执行atexit注册过的函数等)。
这个函数的功能是当进程正常退出时,将调用使用atexit注册过的函数。使用这个函数需要注意的几点是:
1.注册过的函数的执行顺序就像压栈一样,先进后出,也就是最先注册的最后执行,最后注册的最先执行。
2.可以注册的函数个数一般多至是32个,具体上限需要根据平台支持,可以使用sysconf函数确定具体上限。
3.必须是进程正常退出时才会调用atexit注册过的函数,例如进程调用_exit或_Exit退出时就不会调用atexit注册过的函数,因为进程调用_exit或_Exit时不是正常退出的流程,不会做‘善后工作’。
打扫战场的函数。
在这里插入图片描述

函数qsort()

排序方法有很多种:选择排序,冒泡排序,归并排序,快速排序等。 看名字都知道快速排序是目前公认的一种比较好的排序算法。因为他速度很快,所以系统也在库里实现这个算法,便于我们的使用。 这就是qsort函数(全称quicksort)。它是ANSI C标准中提供的,其声明在stdlib.h文件中,是根据二分法写的,其时间复杂度为n*log(n)

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));

base: 待排序数组,排序之后的结果仍放在这个数组中
num:数组中待排序元素数量
width:各元素的占用空间大小(单位为字节)
compar:指向函数的指针,用于确定排序的顺序(需要用户自定义一个比较函数)
int compar(const void *p1, const void *p2);

如果compar返回值小于0(< 0),那么p1所指向元素会被排在p2所指向元素的左面;
如果compar返回值等于0(= 0),那么p1所指向元素与p2所指向元素的顺序不确定;
如果compar返回值大于0(> 0),那么p1所指向元素会被排在p2所指向元素的右面。

各种类型的不同用法,可以参考文章《C语言qsort函数用法》
命令行能做的,函数都能做了。
在这里插入图片描述

函数alarm

alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号。可以设置忽略或者不捕获此信号,如果采用默认方式其动作是终止调用该alarm函数的进程。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

seconds:指定秒数
函数返回值
成功:如果调用此alarm()前,进程已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
出错:-1

函数pause

调用该函数(系统调用)的进程将处于阻塞状态(主动放弃cpu),直到有信号递达将其唤醒。

#include <unistd.h>
int pause(void);   

返回值:-1 并设置errno为EINTR 该函数只有一个返回值,可以理解为只有成功返回值,且为-1,同时errno的值置为EINTR。
注意,只有当一个信号递达且处理方式被捕捉时,pause函数引起挂起操作的进程才会被唤醒,而且只有当信号处理完后(调用完用户处理函数),pause函数才返回-1,且errno置EINTR,进程被唤醒继续执行后面的程序。如果信号的处理方式为默认处理方式或者忽略(丢弃),那么pause函数不会返回值,且进程也不会被激活,而是一直阻塞(挂起)。
pause和alarm二者组合起来可以实现休眠唤醒的。
在这里插入图片描述

函数signal()

C 库函数 void (*signal(int sig, void (*func)(int)))(int) 设置一个函数来处理信号,即带有 sig 参数的信号处理程序。
在这里插入图片描述
这个函数就是可以处理别人发给本进程的所有消息。
在这里插入图片描述

函数sigaction()

一、函数原型:sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作)

int sigaction(int signum, const struct sigaction *act,   struct sigaction *oldact);

signum参数指出要捕获的信号类型,act参数指定新的信号处理方式,oldact参数输出先前信号的处理方式(如果不为NULL的话)。

二、 struct sigaction结构体介绍

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}

sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。
SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号。

例如临时修改某个信号的功能,就可以用这个函数,记得用完要还原哦
在这里插入图片描述

函数getrlimit()/setrlimit()

获取或设置资源使用限制,linux下每种资源都有相关的软硬限制,软限制是内核强加给相应资源的限制值,硬限制是软限制的最大值。
非授权调用的进程只能将其软限制指定为0~硬限制范围中的某个值,同时能不可逆转地降低其硬限制。
授权进程可以任意改变其软硬限制。

原型

int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);

参数resource

类型含义
RLIMIT_AS进程的最大虚内存空间,字节为单位。
RLIMIT_CORE内核转存文件的最大长度。
RLIMIT_CPU最大允许的CPU使用时间,秒为单位。当进程达到软限制,内核将给其发送SIGXCPU信号,这一信号的默认行为是终止进程的执行。然而,可以捕捉信号,处理句柄可将控制返回给主程序。如果进程继续耗费CPU时间,核心会以每秒一次的频率给其发送SIGXCPU信号,直到达到硬限制,那时将给进程发送 SIGKILL信号终止其执行。
RLIMIT_DATA进程数据段的最大值。
RLIMIT_FSIZE进程可建立的文件的最大长度。如果进程试图超出这一限制时,核心会给其发送SIGXFSZ信号,默认情况下将终止进程的执行。
RLIMIT_LOCKS进程可建立的锁和租赁的最大值。
RLIMIT_MEMLOCK进程可锁定在内存中的最大数据量,字节为单位。
RLIMIT_MSGQUEUE进程可为POSIX消息队列分配的最大字节数。
RLIMIT_NICE进程可通过setpriority() 或 nice()调用设置的最大完美值。
RLIMIT_NOFILE指定比进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误。
RLIMIT_NPROC用户可拥有的最大进程数。
RLIMIT_RTPRIO进程可通过sched_setscheduler 和 sched_setparam设置的最大实时优先级。
RLIMIT_SIGPENDING用户可拥有的最大挂起信号数。
RLIMIT_STACK最大的进程堆栈,以字节为单位。

参数rlim结构哦如下

struct rlimit {
  rlim_t rlim_cur;
  rlim_t rlim_max;
};

rlim_t可以理解为int类型的值,分别表示当前大小和硬件限制大小。如果值为RLIM_INFINITY,表示不对资源限制。

成功执行时,返回0。失败返回-1,errno被设为以下的某个值

类型含义
EFAULTrlim指针指向的空间不可访问
EINVAL参数无效
EPERM增加资源限制值时,权能不允许

软件的健壮性应该是可以从这个函数的使用上得到体现。
在这里插入图片描述

函数chdir()

函数名: chdir
功 能: 改变工作目录
用 法:

#include <dir.h>
int chdir(const char *path);

程序例:

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

char old_dir[MAXDIR];
char new_dir[MAXDIR];

int main(void)
{
   if (getcurdir(0, old_dir))
   {
      perror("getcurdir()");
      exit(1);
   }
   printf("Current directory is: \\%s\n", old_dir);

   if (chdir("\\"))
   {
      perror("chdir()");
      exit(1);
   }

   if (getcurdir(0, new_dir))
   {
      perror("getcurdir()");
      exit(1);
   }
   printf("Current directory is now: \\%s\n", new_dir);

   printf("\nChanging back to orignal directory: \\%s\n", old_dir);
   if (chdir(old_dir))
   {
      perror("chdir()");
      exit(1);
   }

   return 0;
}

主要用来判断一些局部调用,保证路径的正确。
在这里插入图片描述

函数setgroups()

设置组代码函数
函数说明:setgroups()用来将list 数组中所标明的组加入到目前进程的组设置中. 参数size 为list()的gid_t 数目, 最大值为NGROUP(32)。

#include <grp.h>
int setgroups(size_t size, const gid_t * list);

返回值:设置成功则返回0, 如有错误则返回-1.

错误代码:
EFAULT:参数list 数组地址不合法.
EPERM:权限不足, 必须是root 权限
EINVAL:参数size 值大于NGROUP(32).

这个还不知道干啥用,可能是为了限制有些权限吧。
在这里插入图片描述

函数setuid()、setgid()

#include <sys/types.h>
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);

改变用户/组ID的规则

1、若进程具有超级用户权限,则setuid将实际用户ID、有效用户ID、保存的设置用户ID设置为uid
2、若进程没有超级用户权限,但uid等于实际用户ID或保存的设置用户ID,则setuid只将有效用户ID设置为uid,不改变实际用户ID和保存的设置用户ID
3、若以上条件不满足,返回-1,errno设为EPERM

只有超级用户进程可以更改实际用户ID
实际用户ID是在用户登录时,由login程序设置的
login是一个超级用户进程,当它调用setuid时,会设置所有三个用户ID

仅当对程序文件设置了设置用户ID位时,exec才会设置有效用户ID。任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或保存的设置用户ID

保存的设置用户ID是由exec复制有效用户ID而得来的。

结束语

今天就来学习一下这篇获奖文章吧。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖哥王老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值