祝各位道友念头通达
GitHub Gitee 语雀 打赏

C语言技巧

生产者如何唤醒消费者(并发编程)

适用于线程对资源占用比较高,延迟高的情况下, 可以有效的降低CPU的利用率
pthread_cond_signalpthread_cond_wait 的引用
生产者

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_producer = PTHREAD_COND_INITIALIZER;
....
    while(1) {
        ...
        qlen = recvfrom(vdata->udp_data_socket, rbuf, rlen, 0, (struct sockaddr *)&client_addr, &blen);
        LOG_DEBUG("recv udp data");
        pthread_cond_signal(&has_producer);  //当有消息过来的时候, 主动唤醒消费者线程去执行
        sleep(3);
    }

消费者

#include <pthread.h>
extern pthread_mutex_t mutex;
extern pthread_cond_t has_producer;
void * scan_thread() {
    prctl(PR_SET_NAME, "Scan");
    // 利用互斥锁, 降低CPU资源的消耗, 但是会陷入内核态,进程会出现上下文两次的切换, 延时大概从几十微妙到几毫秒
    while(1) {
        // pthread_mutex_lock(&mutex);     //加锁
        while (1) {
            int res = pthread_cond_wait(&has_producer, &mutex);   //我们通常在一个循环内使用该函数
            if(res == 0) {
                break;
            }
        }
        LOG_DEBUG("消费者启动 ... ");
        sleep(1);
    }
}

void usr_app() {
    pthread_t scan_threadId;
    pthread_create(&scan_threadId, NULL, scan_thread, (void *)NULL);    
}

GUN 扩展 参数类型判断

typeof__builtin_types_compatible_p 都是GUN的扩展函数

#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
1. "typeof" 获取某个参数 的类型
2. "__builtin_types_compatible_p", 比较两个参数的类型是否相同, 0: 相同, 1: 不同

#define max(x, y) ({                \
    typeof(x) _max1 = (x);          \
    typeof(y) _max2 = (y);          \
    (void) (&_max1 == &_max2);      \//如果调用者传参时,两者类型不一致,在编译时就会发出警告。
    _max1 > _max2 ? _max1 : _max2; })

linux C likely 和 unlikely 区别

<linux/compiler>
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

__builtin_expectgcc(version >= 2.96)的内建函数, 提供给程序员使用, 对判断分支进行优化,以减少指令跳转带来的性能下降
__buildin_expect((x), 1): 表示x的值为真的可能性更大
__buildin_expect((x), 0): 表示x的值为假的可能性更大
编译器编译过程中, 会将可能性更大的代码语句紧跟着后面的代码,从而减少指令跳转带来的性能下降

if (likely(a>b)) {
    fun1();
}

代码在编译的时候, a > b 的可能性很大, 这样cache在预读数据时就可以将 fun1()函数的二进制代码拿到cache中, 这样就添加了 cache的命中率。

对于如何提高cache的命中率

如何提高cache命中率: https://blog.csdn.net/Binp0209/article/details/101847799
cache回写策略: https://blog.csdn.net/xingzhe22222/article/details/81988101

printf 打印输出日期,输出文件,输出文件行数,输出方法名称, 颜色设置, 日期转换

  1. 简单的示例
//1. 打印出日期,文件打印输出日期,输出文件,输出文件行数,输出代码方法名称
// 此处的日期和时间是代码编译时候的日期和时间, 并非系统的时间和日期, 可以针对不同的系统, 对日期进行自定义宏方法打印指定格式的日期, 对于单片机而言, 是没有时间的概念的,可以打印程序运行时晶振震动的次数,或者是在串口打印输出的时候可用本机的时间
#define LOG_INFO(format, ...)     printf("["__DATE__" "__TIME__" " __FILE__" %s %d] " format "\n", __FUNCTION__,  __LINE__,  ##__VA_ARGS__)

//2. 颜色区分输出
printf("\033[5;0;34m显示方式;字背景颜色;字体颜色字符串\033[0m \n" );
printf("\033[5;0;32m显示方式;字背景颜色;字体颜色字符串\033[0m \n" );
printf("\033[5;47;32m显示方式;\033[0m\033[5;0;34m字背景颜色;字体颜色字符串\033[0m \n" );
  1. 日志输出代码整合代码
#define     _GRAY           "\033[5;0;30m" 
#define     _READ           "\033[5;0;31m" 
#define     _GREEN_         "\033[5;0;32m"
#define     _YELLOW         "\033[5;0;33m" 
#define     _BLUE           "\033[5;0;34m" 
#define     _PURPLE         "\033[5;0;35m"
#define     _DARKGREN       "\033[5;0;36m"
#define     _WHITE          "\033[5;0;37m"
#define     _DEFAULT        "\033[5;0;0m"
#define     _COREND         "\033[5m"   
#define     _F_NAME_LEN     10
#define     _FUN_NAME_LEN   10

#define     __YEAR          ((((__DATE__ [7] - '0') * 10 + (__DATE__ [8] - '0')) * 10 + (__DATE__ [9] - '0')) * 10 + (__DATE__ [10] - '0')) 
#define     __MONTH         (__DATE__ [2] == 'n' ? (__DATE__ [1] == 'a' ? 0 : 5)  \
                            : __DATE__ [2] == 'b' ? 1 \
                            : __DATE__ [2] == 'r' ? (__DATE__ [0] == 'M' ? 2 : 3) \
                            : __DATE__ [2] == 'y' ? 4 \
                            : __DATE__ [2] == 'l' ? 6 \
                            : __DATE__ [2] == 'g' ? 7 \
                            : __DATE__ [2] == 'p' ? 8 \
                            : __DATE__ [2] == 't' ? 9 \
                            : __DATE__ [2] == 'v' ? 10 : 11)
#define     __DATE          ((__DATE__ [4] == ' ' ? 0 : __DATE__ [4] - '0') * 10 + (__DATE__ [5] - '0'))

#define     LOG_DEBUG(format, ...)      printf(_BLUE  "%04d_%02d_%02d "__TIME__ _BLUE   " [DEBUG] " _PURPLE "%*s:" _DARKGREN "%-3d " _YELLOW "%-*s: \033[0m" format "", __YEAR, __MONTH, __DATE, _F_NAME_LEN, __FILE__, __LINE__, _FUN_NAME_LEN, __FUNCTION__, ##__VA_ARGS__)
#define     LOG_INFO(format, ...)       printf(_GREEN_"%04d_%02d_%02d "__TIME__ _GREEN_ " [INFO ] " _PURPLE "%*s:" _DARKGREN "%-3d " _YELLOW "%-*s: \033[0m" format "", __YEAR, __MONTH, __DATE, _F_NAME_LEN, __FILE__, __LINE__, _FUN_NAME_LEN, __FUNCTION__, ##__VA_ARGS__)
#define     LOG_WARN(format, ...)       printf(_GRAY  "%04d_%02d_%02d "__TIME__ _GRAY   " [WARN ] " _PURPLE "%*s:" _DARKGREN "%-3d " _YELLOW "%-*s: \033[0m" format "", __YEAR, __MONTH, __DATE, _F_NAME_LEN, __FILE__, __LINE__, _FUN_NAME_LEN, __FUNCTION__, ##__VA_ARGS__)
#define     LOG_ERROR(format, ...)      printf(_READ  "%04d_%02d_%02d "__TIME__ _READ   " [ERROR] " _PURPLE "%*s:" _DARKGREN "%-3d " _YELLOW "%-*s: \033[0m" _READ format "\033[0m", __YEAR, __MONTH, __DATE, _F_NAME_LEN, __FILE__, __LINE__, _FUN_NAME_LEN, __FUNCTION__, ##__VA_ARGS__)

日志文件输出如下
image

该输出方法在串口上也可输出打印, 不过一般串口工具对颜色没有识别, mobaxterm 该工具对串口输出会有对应的颜色打印
image

输出类型

#include <stdio.h>
/**
 * ./a.out 0> test.log, 1,2,3,4 都输出到屏幕
 * ./a.out 1> test.log, 1和2 输出到文件, 3和4输出到屏幕
 * ./a.out 2> test.log, 1和2 输出到屏幕, 3和4输出到文件
 * ./a.out 2> test.log, 1和2 输出到屏幕, 3和4输出到文件
 * ./a.out 1> test.log 2>test1.log, 1和2 输出到文件test.log, 3和4输出到文件test1.log
 * 
*/
int main(int argc, char const *argv[])
{
    printf("--->1, printf \n");  
    fprintf(stdout,"--->2, fprintf - stdout \n");  
    perror("--->3, perror \n");  
    fprintf(stderr,"--->4, fprintf stderr \n"); 
    return 0;
}

宏定义使用技巧

// 宏定义,判断是否OK,否则输出错误对应的函数和行数
#define ATTEMPT(xFuncToTry) {if(xFuncToTry != MXD_OK) \
                            {MXD_DBG_ERROR("ATTEMPT failed at %s:%u\n",__FUNCTION__,__LINE__); \
                            return MXD_FAIL;}}

// printf 宏定义
#define IIC_DEBUG(...)              printf(__VA_ARGS__)

单#使用

"#" 强制将 attr 转换为字符串, 所以以下程序输出 "iRet, 10"

#include <stdio.h>
#define __TEST(attr)      printf("%s, %d \n", #attr, attr);

int main (int argc, char **argv)
{
    printf("Hello SylixOS!\n");
    int iRet = 10;
    __TEST(iRet);
    return  (0);
}

双## 使用

#include <iostream>
void quit_command(){
    printf("I am quit command\n");
}
void help_command(){
    printf("I am help command\n");
}
struct command
{
    char * name;
    void (*function) (void);
};
#define COMMAND(NAME) {#NAME,NAME##_command}
#define PRINT(NAME) printf("token"#NAME"=%d\n", token##NAME)
main(){
    int token9=9;
    PRINT(9);
    struct command commands[] = {
        COMMAND(quit),
        COMMAND(help),
    };
    commands[0].function();
}

extern 和 static

在linux C 环境下, 不同的编译器有可能对static处理方式不同

  1. cpp1 在两个文件中是隔离开的
  2. cpp 是两个文件共享使用
    A.c
extern int cpp = 10;
static int cpp1 = 11;
int add(int a, int b) {
    printf("B cpp1 %d \n", cpp1);
    return a + b;
}

B.c

int add(int a, int b);
extern cpp;
static int cpp1 = 12;
int main(int argc, char const *argv[])
{
    printf("cpp %d \n", cpp);
    printf("cpp1 %d \n", cpp1);
    printf("4 + 5 = %d \n", add(4, 5));
    return 0;
}

动态链接库使用

linux c 下的驱动程序就是编译成动态xx.ko 加载的

  1. 动态库以 lib 开头
  2. 动态库的创建: 例如 B.c 文件 使用 gcc -shared -o libB.so B.c
  3. 动态库的引用: 编译时引用链接 A.c 使用 gcc -o test A.c -lB
  4. 然后输入 ./test 运行 或者输入 ldd, 发现链接的 libB.so 未找见
  5. 配置.so路径
    A. 直接 export LD_LIBRARY_PATH=你的库的路径:$LD_LIBRARY_PATH
    B. 或者 ln 链接到 /lib或者/lib64
    C. 直接将 libB.so mv到 /lib或者/lib64
  6. 然后再使用 ldd 查看, 然后也能运行
  7. 动态编译库 在 windows 下的后缀名为 "xxx.dll"

注: 如果动态编译的时候, 使用共享库, 则使用 -fPIC 参数编译, 既:

gcc -o test A.c -Wl,-shared -fPIC -shared

静态库的编译使用

  1. 静态库的创建需要使用GUN下的 ar 指令, arm下也有对应的arm-linux-xxx-ar 类似的指令
  2. ar cr libxxx.a 创建一个空的静态库
  3. ar -q libxxx.a xxx.o, 将编译的中间文件 xxx.o 添加至静态库中
  4. 使用 nm或者 arm-linux-xxx-nm 可以查看 xxx.olibxxx.a 中的函数链接关系
  5. 使用 ranlib 或者 arm-linux-xxx-ranlib 来更新静态库的符号索引
  6. 在 C 中如何引用静态库: 比如在同一个目录下, 静态库为 "libtest.a", 则编译时使用以下指令, 然后用下面指令编译
  7. 静态编译库 在 windows 下的后缀名为 "xxx.lib"
gcc test.c -L./ -ltest
  1. 在引用方法的时候需要声明一下, 然后就可以使用

errno(用户态)

当调用系统的一些函数的时候, 如socket, 文件等相关接口返回-1,需要知道具体错误结果,可以直接使用 errno, 它保存了最后系统一次出现错误的状态

#include <linux/errno.h>
//或者是 
#include <errno.h>
//erron 错误码
//strerror(errno) 错误码的解释
printf("%d : %s",errno,strerror(errno));

err.h (内核态)

在内核态中, 如果调用一个函数返回指针,但是如果调用出错的话指针返回为NULL,然后根据对指针的解析可以解析出其错误状态码

#include <linux/err.h>
#define MAX_ERRNO	4095
#define IS_ERR_VALUE(x) unlikely((unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO)
static inline bool __must_check IS_ERR(__force const void *ptr)
{
	return IS_ERR_VALUE((unsigned long)ptr);
}
static inline long __must_check PTR_ERR(__force const void *ptr)
{
	return (long) ptr;
}

使用示例

//返回指针为NULL的时候, 对其错误码的处理
var *ptr = xxxx();
if(IS_ERR(ptr)) {       //根据返回NULL指针判断返回状态是否有错
	ret = PTR_ERR(ptr); //获取错误状态值
	goto out3;
}

内联函数 inline

内联函数编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。但在选择使用内联函数时,必须在程序占用空间和程序执行效率之间进行权衡,因为过多的比较复杂的函数进行内联扩展将带来很大的存储资源开支
内联函数与宏定义区别

  1. 内联函数是在编译时展开,而宏在预编译时展开;在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。
  2. 内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能;宏不是函数,而inline是函数。
  3. 宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性。而内联函数不会出现二义性。
  4. inline有点类似于宏定义,但是它和宏定义不同的是,宏定义只是简单的文本替换,是在预编译阶段进行的。而inline的引入正是为了取消这种复杂的宏定义的。
  5. inline 使用的多会增加编译时间。
//inline 使用该关键字修饰的方法, 内联函数主要是为了解决函数调用栈不够的问题
inline void fun(int a, int b) {
	return a + b;
}
void fun1() {
	fun(); //相当于直接将 a + b, 放在次数
}
posted @ 2022-11-04 10:12  韩若明瞳  阅读(102)  评论(0编辑  收藏  举报