阅读源码中的C语言积累 一

关于likely()与unlikely函数

例如,下面是一个条件选择语句:
if (foo) {
    /* .. */
}
如果想要把这个选择标记成绝少发生的分支:
/* 我们认为foo绝大多数时间都会为0.. */
if (unlikely(foo)) {
    /* .. */
}
相反,如果我们想把一个分支标记为通常为真的选择:
/* 我们认为foo通常都不会为0 */
if  (likely(foo)) {
      /* .. */
}

Static inline 函数
内联函数的代码会被直接嵌入在它被调用的地方,调用几次就嵌入几次,没有使用call指令。这样省去了函数调用时的一些额外开销,比如保存和恢复函数返回地址等,可以加快速度。不过调用次数多的话,会使可执行文件变大,这样会降低速度。相比起宏来说,内核开发者一般更喜欢使用内联函数。因为内联函数没有长度限制,格式限制。编译器还可以检查函数调用方式,以防止其被误用。
union的使用
在这里插入图片描述
在这里插入图片描述
enum枚举类型:

如果不用枚举
#define MON 1 
#define TUE 2 
#define WED 3 
#define THU 4 
#define FRI 5 
#define SAT 6
#define SUN 7
代码量看起来非常多
enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};

C语言中可变参数的用法
在C语言程序编写中我们使用最多的函数一定包括printf以及很多类似的变形体。这个函数包含在C库函数中,定义为 int printf( const char* format, …);

#include <iostream.h>
  void fun(int a, ...)
  {
    int *temp = &a;
    temp++;  //评论中反馈有问题,待改中
    for (int i = 0; i < a; ++i)
    {
      cout << *temp << endl;
      temp++;
    }
  }
  int main()
  {
    int a = 1;
    int b = 2;
    int c = 3;
    int d = 4;
    fun(4, a, b, c, d);
    system("pause");
    return 0;
  }
  Output::
  1
  2
  3
  4

attribute((unused)) 的含义
有些函数没有使用或者变量没有使用,告诉编译器忽略此警告。

线程取消(pthread_cancel)
 pthread_cancel调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,
 
linux 下的进程退出
1) SIGINT关联ctrl+c
2)SIGINT只能结束前台进程
2) 1)SIGTERM可以被阻塞、处理和忽略;因此有的进程不能按预期的结束

Linux下sigaction
sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作)
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact)
函数说明:sigaction()会依参数signum指定的信号编号来设置该信号的处理函数
函数参数:
signum是指定信号的编号,除SIGKILL和SIGSTOP信号以外
act参数如下:

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

Strdup拷贝串函数

C开发之----#if、#ifdef、#if defined之间的区别
#if的使用说明
#if的后面接的是表达式
#if (MAX10)||(MAX20) code… #endif
它的作用是:如果(MAX10)||(MAX20)成立,那么编译器就会把其中的#if 与 #endif之间的代码编译进去(注意:是编译进去,不是执行!!)
#if defined的使用
#if后面接的是一个宏。
#if defined (x) …code… #endif
这个#if defined它不管里面的“x”的逻辑是“真”还是“假”它只管这个程序的前面的宏定义里面有没有定义“x”这个宏,如果定义了x这个宏,那么,编译器会编译中间的…code…否则不直接忽视中间的…code…代码。
另外 #if defined(x)也可以取反,也就用 #if !defined(x)
#ifdef的使用
#ifdef的使用和#if defined()的用法一致
#ifndef又和#if !defined()的用法一致。
最后强调两点:
第一:这几个宏定义只是决定代码块是否被编译!
第二:别忘了#endif

取消线程:
(1)一个线程可以调用pthread_cancel来取消另一个线程。
(2)被取消的线程需要被join来释放资源。
(3)被取消的线程的返回值为PTHREAD_CANCELED

有关线程的取消,一个线程可以为如下三个状态: 
     (1)可异步取消:一个线程可以在任何时刻被取消。 
     (2)可同步取消:取消的请求被放在队列中,直到线程到达某个点,才被取消。
     (3)不可取消:取消的请求被忽略。 
       默认状态下,线程是可同步取消的。 

 调用pthread_setcanceltype来设定线程取消的方式: 
           pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL); //异步取消、 
           pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED, NULL); //同步取消、 
           pthread_setcanceltype (PTHREAD_CANCEL_DISABLE, NULL);//不能取消 

互斥锁mutex的使用方法
一,锁的创建

锁可以被动态或静态创建,可以用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁,采用这种方式比较容易理解,互斥锁是pthread_mutex_t的结构体,而这个宏是一个结构常量,如下可以完成静态的初始化锁:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

另外锁可以用pthread_mutex_init函数动态的创建,函数原型如下:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)

二,锁的属性
pthread_mutexattr_init初始化锁的属性
三,锁的释放
调用pthread_mutex_destory之后,可以释放锁占用的资源,但这有一个前提上锁当前是没有被锁的状态。
四,锁操作

对锁的操作主要包括加锁 pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个。

int pthread_mutex_lock(pthread_mutex_t *mutex)

int pthread_mutex_unlock(pthread_mutex_t *mutex)

int pthread_mutex_trylock(pthread_mutex_t *mutex)

pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待

五,锁的使用
见pthread程序

pthread_cond_wait() 用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。 pthread_cond_wait() 必须与pthread_mutex
配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread_cond_signal()或pthread_cond_broadcast,把该线程唤醒,使pthread_cond_wait()通过(返回)时,该线程又自动获得该mutex。
pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
使用pthread_cond_signal一般不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。
但是pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程.

CPU的缓存架构:
在这里插入图片描述
CPU缓存行
缓存是由缓存行组成的,通常是 64 字节(常用处理器的缓存行是 64 字节的,比较旧的处理器缓存行是 32 字节),并且它有效地引用主内存中的一块地址。
在这里插入图片描述
如果访问一个 long 类型的数组时,当数组中的一个值被加载到缓存中时,另外 7 个元素也会被加载到缓存中。
但是,如果使用的数据结构中的项在内存中不是彼此相邻的,比如链表,那么将得不到免费缓存加载带来的好处。
不过,这种免费加载也有一个坏处。设想如果我们有个 long 类型的变量 a,它不是数组的一部分,而是一个单独的变量,并且还有另外一个 long 类型的变量 b 紧挨着它,那么当加载 a 的时候将免费加载 b。
看起来似乎没有什么毛病,但是如果一个 CPU 核心的线程在对 a 进行修改,另一个 CPU 核心的线程却在对 b 进行读取。
当前者修改 a 时,会把 a 和 b 同时加载到前者核心的缓存行中,更新完 a 后其它所有包含 a 的缓存行都将失效,因为其它缓存中的 a 不是最新值了。
而当后者读取 b 时,发现这个缓存行已经失效了,需要从主内存中重新加载。
请记住,我们的缓存都是以缓存行作为一个单位来处理的,所以失效 a 的缓存同时,也会把 b 失效,反之亦然。
在这里插入图片描述
这样就出现了一个问题,b 和 a 完全不相干,每次却要因为 a 的更新需要从主内存重新读取,它被缓存未命中给拖慢了。
这就是传说中的伪共享。

linux编程之pipe()函数
管道是一种把两个进程之间的标准输入和标准输出连接起来的机制,从而提供一种让多个进程间通信的方法,当进程创建管道时,每次
都需要提供两个文件描述符来操作管道。其中一个对管道进行写操作,另一个对管道进行读操作。对管道的读写与一般的IO系统函数一
致,使用write()函数写入数据,使用read()读出数据。
int pipe(int filedes[2]);
返回值:成功,返回0,否则返回-1。参数数组包含pipe使用的两个文件的描述符。fd[0]:读管道,fd[1]:写管道。
必须在fork()中调用pipe(),否则子进程不会继承文件描述符。两个进程不共享祖先进程,就不能使用pipe。但是可以使用命名管道。

Write函数
write(STDOUT_FILENO, buf, len);向屏幕输出一个buf,长度为len
STDIN_FILENO:接收键盘的输入
STDOUT_FILENO:向屏幕输出
execlp
从PATH 环境变量中查找文件并执行
execlp(“ls”, “ls”, NULL); //ls输出结果默认对应屏幕
execlp(“wc”, “wc”, “-l”, NULL); //wc命令默认从标准读入取数据
定义:
int execlp(const char * file,const char * arg,……);//文件,参数,NULL

发布了27 篇原创文章 · 获赞 20 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_33479881/article/details/103159194