Skip to main content
 首页 » 编程设计

C经典案例

2022年07月19日157Leo_wl

1. C中可变参数函数作为函数参数:

void media_debug_set_handler(struct media_device *media, void (*debug_handler)(void *, ...), void *debug_priv) 
调用: 
media_debug_set_handler(media, (void (*)(void *, ...))fprintf, stdout);

2.可变参数函数

gstcaps.c 
 
GstCaps *gst_caps_new_full (GstStructure * struct1, ...) 
{ 
  GstCaps *caps; 
  va_list var_args; 
 
  va_start (var_args, struct1); 
  caps = gst_caps_new_full_valist (struct1, var_args); 
  va_end (var_args); 
 
  return caps; 
} 
 
GstCaps * gst_caps_new_full_valist (GstStructure * structure, va_list var_args) 
{ 
  GstCaps *caps; 
 
  caps = gst_caps_new_empty (); 
 
  while (structure) { 
    gst_caps_append_structure_unchecked (caps, structure); 
    structure = va_arg (var_args, GstStructure *); 
  } 
 
  return caps; 
}

3.offsetof实现

#include <stddef.h>中  #define offsetof(TYPE, MEMBER) ((int)&((TYPE *)0)->MEMBER)

疑问:为什么自己同样实现报将指针强制类型转换为int,使用头文件中的怎么没有呢 ?

4.C的精髓

#include <stdio.h> 
#include <stdlib.h> 
 
void getArray(long *p) { 
    int i, j, n = 0; 
    int *p1 = malloc(256); 
 
    for (i = 0; i < 5; i++) { 
        for (j = 0; j < 5; j++) { 
            p1[n++] = n; 
        } 
    } 
 
    *p = (long)p1; 
} 
 
 
void printArray(void **p) { 
    int i, j; 
    int (*p1)[5] = (int (*)[])p; 
    //int **p1 = (int **)p; //oops,for different deference protocoal 
 
    for (i = 0; i < 5; i++) { 
        for (j = 0; j < 5; j++) { 
            printf("[%d %d]", p1[i][j], *(*(p1+i)+j)); //array pointer also can use ** to deference 
        } 
        printf("\n"); 
    } 
} 
 
int main() { 
    int **p = NULL; 
 
    getArray((long *)&p); //三级指针可以强制转换成一级指针进行传参,印证只有值传递 
 
    printArray((void **)p); 
 
    free((void *)p); 
 
    return 0; 
}

5.GNU扩展

int main() { 
    int i; 
    char a[10] = {[0 ... 5] = 10}; 
 
    for (i = 0; i < 10; i++) { 
        printf("a[%d] = %d\n", i, a[i]); 
    } 
 
    return 0; 
}

结构体数组中数组的初始化:

struct cpufreq cf[NR_CLUS] = { 
    { 
        .freqs[0 ... NR_FREQ - 1] = UINT_MAX, 
    }, 
    { 
        .freqs[0 ... NR_FREQ - 1] = UINT_MAX, 
    }, 
}

6.errno

errno 是记录系统的最后一次错误代码,错误代码定义在Linux内核的errno-base.h中。errno是一个int型的值,在errno.h中定义。
当linux C api函数发生异常时, 一般会将errno变量(需include errno.h)赋一个整数值, 不同的值表示不同的含义,可以通过查看该
值推测出错的原因。

注意errno记录的是最后一次出错的错误代码,感兴趣的错误代码可能被最新的错误覆盖!

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <stdio.h> 
#include <string.h> 
#include <errno.h> 
 
void main()  
{ 
    printf("errno = %d\n", errno); // errno=0 
    // chmod 444 tmp.txt 
    int fd = open("tmp.txt", O_RDWR); //此时errno=13: Permission denied 
    if (fd < 0) { 
        // no log.txt 
        fd = open("log.txt", O_RDWR); 
        printf("error: %s\n", strerror(errno)); //由于log.txt不存在,此处报的是No such file or directory 
    } 
}

注:在进行代码插桩的时候,若是不像破坏之前的 errno 值,可以在插桩之前先将 errno 值缓存起来。

7. C中左右两边都可以的强制类型转换

#include <stdio.h> 
#include <stdlib.h> 
 
void* get_mem(){ 
    return malloc(8); 
} 
 
void main() 
{ 
    float *p1; 
    int *p2, *p3; 
    p1 = (float*)get_mem(); 
    printf("p1=%p\n", p1); 
    p3 = (int*)p1;       //在右边进行强制类型转换 
    *(float**)&p2 = p1;  //在左边进行强制类型转换 
    printf("p2=%p\n", p2); 
    printf("p3=%p\n", p3); 
    printf("Hello World\n"); 
    free(p1); 
}

&p2是一个常量,(float**)指定为地址,解引用为向此常量地址存储空间中写入值,此地址刚好就是p2指针的存储空间。

8. 宏中的“##args”代表参数列表

#include <stdio.h> 
 
#define v4l2_subdev_call(sd, f, args...) f((sd) , ##args) 
 
int test_func(int a, int b, int c, int d) 
{ 
    printf("a= %d\nb= %d\nc= %d\nd= %d\n", a, b, c, d); 
    return 0; 
} 
 
void main() 
{ 
    int (*p_func)(int, int, int, int) = test_func; 
    v4l2_subdev_call(10, p_func, 20, 30, 40); 
} 
 
/* 
/work/test/v4l2_test# ./pp 
a= 10 
b= 20 
c= 30 
d= 40 
*/

9. 若实现框架时事先不知道对象的类型,可以定义一个联合体进行覆盖。然后使用哪个域交由使用者决定。

union v4l2_ctrl_ptr { 
    s32 *p_s32; 
    s64 *p_s64; 
    u8 *p_u8; 
    u16 *p_u16; 
    u32 *p_u32; 
    char *p_char; 
    void *p; 
};

10.多次使用define和undef来实现C配置文件,参考features.h和kernel\sched\sched.h

#define SCHED_FEAT(name, enabled) 
... 
#undef SCHED_FEAT

11. 逗号运算符

#define do_div(n, base) ({                \ 
    uint32_t __base = (base);            \ 
    uint32_t __rem;                        \ 
    __rem = ((uint64_t)(n)) % __base;    \ 
    (n) = ((uint64_t)(n)) / __base;        \ 
    __rem;                                \ 
 })

参数 n 存放商,返回值为余数。这种"宏函数"可以达到传引用一样的效果,并且两个参数可以是任意的“东西”。不想让这个宏起作用了,就 #undef 它。

12. 处理溢出

#include <stdio.h> 
 
#define CHAR_MAX 0xff 
 
void main() 
{ 
    int i, delta; 
    char old = 0xfe; 
    char new = old; 
     
    for (i = 0; i < 6; i++) { 
        new++; 
        if (new < old) { 
            delta = new + (CHAR_MAX - old); 
        } else { 
            delta = new - old;             
        } 
        printf("delta=%d\n", delta); 
    } 
} 
 
/* 
$ ./pp 
delta=1 
delta=2 
delta=3 
delta=4 
delta=5 
delta=6 
*/

只能顶一次溢出。

13. 二维数组与一维数组指针:

#include <stdio.h> 
 
/*根据结合性,pp是一个指向一维数组的指针,指向的数组中的每个元素是int*类型的指针*/ 
int* (*pp)[3]; 
 
int a = 0x11; 
int b = 0x22; 
int c = 0x33; 
int d = 0x44; 
int e = 0x55; 
int f = 0x66; 
 
void main() 
{ 
    int i, j; 
    /*根据结合性,pa是一个二维数组,数组中的每个元素是int *类型的指针*/ 
    int *pa[2][3] = {{&a, &b, &c},{&d, &e, &f}}; 
    for(i = 0; i < 2; i++) { 
        for(j = 0; j < 3; j++) { 
            printf("pa[%d][%d]=0x%x\n", i, j, *pa[i][j]); 
        } 
    } 
 
    /*二维数组名其实是一个指向一维数组的指针,两者类型相同可以赋值*/ 
    pp = pa; 
    for(i = 0; i < 2; i++) { 
        for(j = 0; j < 3; j++) { 
            printf("pp[%d][%d]=0x%x\n", i, j, *pp[i][j]); 
        } 
    } 
}

14. 三维数组与二维数组指针

#include <stdio.h> 
 
/*根据结合性,pp是一个指向二维数组的指针,指向的数组中的每个元素是int*类型的指针*/ 
int* (*pp)[2][3]; 
 
int aa = 0x11; 
int bb = 0x22; 
int cc = 0x33; 
int dd = 0x44; 
int ee = 0x55; 
int ff = 0x66; 
int gg = 0x77; 
int hh = 0x88; 
int ii = 0x99; 
int jj = 0xaa; 
int kk = 0xbb; 
int ll = 0xcc; 
 
void main() 
{ 
    int i, j, h; 
    /*根据结合性,pa是一个三维数组,数组中的每个元素是int *类型的指针*/ 
    int *pa[2][2][3] = {{{&aa, &bb, &cc},{&dd, &ee, &ff}}, {{&gg, &hh, &ii}, {&jj, &kk, &ll}}}; 
    for (h = 0; h < 2; h++) { 
        for(i = 0; i < 2; i++) { 
            for(j = 0; j < 3; j++) { 
                printf("pa[%d][%d][%d]=0x%x\n", h, i, j, *pa[h][i][j]); 
            } 
        } 
    } 
 
    /*三维数组名其实是一个指向二维数组的指针,两者类型相同可以赋值*/ 
    pp = pa; 
    for (h = 0; h < 2; h++) { 
        for(i = 0; i < 2; i++) { 
            for(j = 0; j < 3; j++) { 
                printf("pp[%d][%d][%d]=0x%x\n", h, i, j, *pp[h][i][j]); 
            } 
        } 
    } 
}

15. 编译过程中stringfy的 # 比宏展开还先处理

#include <stdio.h> 
 
#define HELLO 112233 
#define stringfy(x) #x 
 
void main() 
{ 
    printf("%s\n", stringfy(HELLO)); //打印HELLO而不是112233 
}

16. 编译过程中链接符 ## 也是比宏先展开处理

#include <stdio.h> 
 
#define Hello_WORLD "Hello_WORLD" 
#define Hello_World "Hello_World" 
 
#define WORLD World 
#define JINJIN(x) Hello_##x 
 
void main() 
{ 
    printf("%s\n", JINJIN(WORLD)); 
} 
 
/* 
$ ./pp 
Hello_WORLD 
 
先对 ## 进行解析后再宏替换。 
*/

17. 不使用sizeof获取结构体大小,附带获取结构体内成员偏移

#include <stdio.h> 
 
#define size(type)  (unsigned long)&((type*)0)[1] 
#define offset_of(type, val)  (unsigned long)&(((type*)0)->val) 
 
struct task { 
    int a; 
    char b; 
    long c; 
    short d; 
}; 
 
void main() 
{ 
    printf("use szie=%ld\n", size(struct task)); //24 获取结构体大小 
    printf("use szieof=%ld\n", sizeof(struct task)); //24 
    printf("use offset_of=%ld\n", offset_of(struct task, c)); //8 获取成员偏移位置 
}

18. 对结构体数组进行 typedef

#include <stdio.h> 
 
struct stu { 
    int age; 
    int grade; 
}; 
 
typedef struct stu stu_t[1]; 
 
void init_stu(stu_t *t) 
{ 
    (*t)[0].age = 0x11; 
    (*t)[0].grade = 0x22; 
} 
 
void main() 
{ 
    stu_t stu1; 
    init_stu(&stu1); 
    printf("0x%x  0x%x\n", stu1[0].age, stu1[0].grade); 
}

linux内核中使用 typedef struct cpumask cpumask_var_t[1] 是为了操作方便,比如 struct stu 中只有一个成员的话可以这样赋值:

void init_stu(stu_t *t) 
{ 
    *(int*)t = 0x11; 
}

19. kernel中可以使用“%pf”格式控制,通过函数指针打印函数的名字,"%pF" 还可以打印出地址和偏移,如:worker_thread+0x90/0x574。(测试用户空间无效)

20. 将相关代码链接后放在一起,如kernel中的"__sched"

/* sched.text 正在努力进行功能对齐以确保我们在生成 System.map 时即使在第二次 ld 传递时也具有相同的地址 */ 
./linux/sched/debug.h:extern char __sched_text_start[], __sched_text_end[]; 
./linux/sched/debug.h:#define __sched  __attribute__((__section__(".sched.text"))) 
 
//include/asm-generic/vmlinux.lds.h 
#define SCHED_TEXT                            \ 
        ALIGN_FUNCTION();                    \ 
        VMLINUX_SYMBOL(__sched_text_start) = .;            \ 
        *(.sched.text)                        \ 
        VMLINUX_SYMBOL(__sched_text_end) = .; 
 
//kernel/sched/core.c 
int in_sched_functions(unsigned long addr) 
{ 
    return in_lock_functions(addr) || 
        (addr >= (unsigned long)__sched_text_start 
        && addr < (unsigned long)__sched_text_end); 
} 
//使用如: 
int __sched mutex_lock_killable(struct mutex *lock)

21. 类似函数传参一样传递宏

#define GENERATE_STRING(name, value) #name, 
#define ASSIGN_VALUE(name, value) .name = value, 
#define DEFINITION(name, value) name; 
#define ENUM_FS(name, value) name = value, 
 
#define FUNCTION_LIST(macro) \ 
    macro(aaa, 0) \ 
    macro(bbb, 1) \ 
    macro(ccc, 2) \ 
    macro(ddd, 3) \ 
    macro(eee, 4) \ 
    macro(fff, 5) \ 
    macro(ggg, 6) \ 
 
 
char *func_str[] = { 
    FUNCTION_LIST(GENERATE_STRING) 
}; 
 
struct func_struct { 
    FUNCTION_LIST(DEFINITION) 
}; 
 
struct func_struct fs = { 
    FUNCTION_LIST(ASSIGN_VALUE) 
}; 
 
enum fs_em = { 
    FUNCTION_LIST(ENUM_FS) 
}; 
 
/* 
$ gcc -E define_test.c 
 
char *func_str[] = { 
 "aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", 
}; 
 
struct func_struct { 
 aaa; bbb; ccc; ddd; eee; fff; ggg; 
}; 
 
struct func_struct fs = { 
 .aaa = 0, .bbb = 1, .ccc = 2, .ddd = 3, .eee = 4, .fff = 5, .ggg = 6, 
}; 
 
enum fs_em = { 
 aaa = 0, bbb = 1, ccc = 2, ddd = 3, eee = 4, fff = 5, ggg = 6, 
}; 
*/

22. 结构体的初始化简便写法,多个成员也行

#define RB_ROOT    (struct rb_root) { NULL, } 
 
struct rb_root { 
    struct rb_node *rb_node; 
}; 
 
static struct rb_root root;
root = RB_ROOT; //对结构体中的成员初始化为NULL

23. 目前gcc编译器支持结构体直接清0初始化(用户空间和内核都支持)

void main() 
{ 
    int i; 
    struct sched_attr attr={}; 
    char *p = (char *)&attr; 
     
    for (i = 0; i < sizeof(attr); i++) { 
        printf("0x%x\n", *p++); 
    } 
}

24. 1e2

/* 
1e0 = 1 x 10^0 
1e2 = 1 x 10^2 
*/ 
void main() 
{ 
    printf("%d\n", 1e2); //以d%格式解析double的打印是错误的 
    printf("%f\n", 1e2); 
} 
 
/* 
$ ./pp 
56904232 
100.000000 
*/

25. 数组初始化两次

#include <stdio.h> 
 
#define __SYSCALL(nr)    [nr] = #nr, 
#define __NR_ 100 
 
//一个元素初始化两次 
const char *sys_call_table[__NR_] = { 
    [0 ... __NR_ - 1] = "nop", 
    __SYSCALL(10) 
    __SYSCALL(20) 
}; 
 
void main() 
{ 
    int i; 
     
    for(i = 0; i < __NR_; i++) { 
        printf("sys_call_table[%d]=%s\n", i, sys_call_table[i]); 
    } 
} 
 
/* 
... 
sys_call_table[9]=nop 
sys_call_table[10]=10 
... 
sys_call_table[19]=nop 
sys_call_table[20]=20 
*/

26. _THIS_IP_ 取一个存放label地址的地址

#include <stdio.h> 
 
#define _THIS_IP_  ({ __label__ __here; __here: (unsigned long)&&__here; }) 
 
void __local_bh_enable_ip(unsigned long ip) 
{ 
    printf("ip=0x%lx\n", ip); 
} 
 
void main() 
{ 
    __local_bh_enable_ip(_THIS_IP_); 
} 
 
/* 
 
$ ./pp 
ip=0x40054f 
 
*/

27. 可变参数传参 ##args 或 ##__VA_ARGS__ 都行。

实现是否打印控制

#include <stdio.h> 
 
#define debug_print1(fmt, args...) do { if(debug_enable) printf(fmt, ##args); }while(0) 
#define debug_print2(fmt, ...)     do { if(debug_enable) printf(fmt, ##__VA_ARGS__); }while(0) 
 
int debug_enable = 1; 
 
void main() 
{ 
    int a = 1; 
    char *p = "Hello World!"; 
     
    debug_print1("a=%d, p=%s\n", a, p); 
    debug_print2("a=%d, p=%s\n", a, p);     
} 
 
/* 
$ ./pp 
a=1, p=Hello World! 
a=1, p=Hello World! 
*/

还可以使用掩码控制打印哪一类log:

//binder.c 
static uint32_t binder_debug_mask = BINDER_DEBUG_USER_ERROR | BINDER_DEBUG_FAILED_TRANSACTION | BINDER_DEBUG_DEAD_TRANSACTION; 
module_param_named(debug_mask, binder_debug_mask, uint, 0644); 
#define binder_debug(mask, x...) \ 
    do { \ 
        if (binder_debug_mask & mask) \ 
            pr_info_ratelimited(x); \ 
    } while (0)

28. 一个易忽略的情况

#include <stdio.h> 
 
#define MY_DEBUG 0 
 
void main() 
{ 
#ifdef MY_DEBUG 
        printf("defined!\n"); 
#else 
        printf("non-defined!\n"); 
#endif 
}

将会打印 "defined!", 而不是“non-defined!”

29. 对结构体中的数组进行赋值

#include <stdio.h> 
#include <string.h> 
 
struct input_device_id { 
    long evbit[4]; 
    long keybit[4]; 
}; 
 
int main() 
{ 
    struct input_device_id id = { 
        .evbit = {[0] = 1<<1}, 
    }; 
 
    printf("%ld\n", id.evbit[0]); //2 
 
    return 0; 
} 
 
/* 
//摘录自sysrq.c: 
static const struct input_device_id sysrq_ids[] = { 
    { 
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, 
        .evbit = { [BIT_WORD(EV_KEY)] = BIT_MASK(EV_KEY) }, 
        .keybit = { [BIT_WORD(KEY_LEFTALT)] = BIT_MASK(KEY_LEFTALT) }, 
    }, 
    { }, 
}; 
*/

30. 结构体对齐

结构体对齐分结构体成员之间对齐和结构体之间的对齐,所谓对齐可以理解为能整除。先了解几个概念:

(1) 自身对齐值:结构体变量里每个成员的自身大小。
(2) 指定对齐值:有宏 #pragma pack(N) 指定的值这里面的N一定是2的幂次方。如1,2,4,8,16等,如果没有通过宏那么在32位Linux主机上默认指定对齐值为4,64位的默认对齐值为8,AMR CPU默认指定对齐值为8。
(3) 有效对齐值(又称结构体成员之间对齐值):结构体成员之间自身对齐时,有效对齐值为自身对齐值与指定对齐值中较小的一个。
(4) 结构体之间对齐值:总体对齐时,大小是 min{所有成员中自身对齐值最大的, 指定对齐值} 的整数倍。

struct stu1 { 
    int a; 
    int b; 
    char c; 
}; 
//Arm64机器上是 12字节。 
 
struct stu2 { 
    int a; 
    int b; 
    long d 
    char e; 
}; 
//Arm64机器上是 24字节。

结构体中包含结构体的:

struct mm { 
    char e; //1字节 
    long f; //空7字节+8字节 
}; //共16字节 
 
struct stu2 { 
    int a; //4字节 
    int b; //4字节 
    struct mm m; //16字节 
    char e; //1字节+空7字节  
}; //共32字节

参考:C语言|结构体内存对齐规则

31. 负数使用补码表示,而打印格式控制%d可打印正负数,而%x打印的是补码

#include <stdio.h> 
#include <string.h> 
 
void main() 
{ 
    int i; 
    char str[2]; 
    char *p = str; //unsigned char *p = str; 
    int t; 
    memset(str, 0xff, sizeof(str)); 
 
    for (i = 0; i < sizeof(str); i++) { 
        printf("p[%d]=0x%x\n", i, p[i]); 
        t = p[i]; 
        printf("t=%d\n", t); 
    } 
} 
 
/* 
char *p = str; 的情况下: 
p[0]=0xffffffff  //-1使用0x%x格式打印是0xffffffff 
t=-1             //%d格式打印-1就是-1 
p[1]=0xffffffff 
t=-1 
--------------------------- 
unsigned char *p = str; 的情况下: 
p[0]=0xff       //0xff使用0x%x格式打印还是0xff 
t=255           //%d格式打印255还是255 
p[1]=0xff 
t=255 
 
*/

31. 使用union避免进行位段操作,内核中使用的有如下例子,这样使用需要区分大小端。

struct thread_info { //arch/arm64/include/asm/thread_info.h 
    ... 
    union { 
        u64        preempt_count;    /* 0 => preemptible, <0 => bug */ 
        struct { 
#ifdef CONFIG_CPU_BIG_ENDIAN 
            u32    need_resched; 
            u32    count; 
#else 
            u32    count; 
            u32    need_resched; //也是搞成一个union,来避免进行位段处理 
#endif 
        } preempt; 
    }; 
    ... 
}; 
 
就是为了方便对val的各个字段进行操作,搞了个union 
typedef struct qspinlock { //include/asm-generic/qspinlock_types.h 
    union { 
        /* 
         * "val"作为一个32位的变量,包含了三个部分:"locked byte", "pending"和"tail","tail"又细分为"tail index"和"tail cpu" 
         * bit0-7: locked byte 
         * bit8: pending (bit9-15和其一起操作) 
         * bit9-15: not used 
         * bit16-17: tail index 
         * bit18-31: tail cpu(+1) 
         */ 
        atomic_t val; 
 
#ifdef __LITTLE_ENDIAN 
        struct { 
            u8    locked; 
            u8    pending; 
        }; 
        struct { 
            u16    locked_pending; 
            u16    tail; 
        }; 
#else 
    ... 
#endif 
    }; 
} arch_spinlock_t;

32.bit map使用(参考内核中cpumask)

//相关宏 
#define BITS_PER_BYTE    8 
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) 
#define BITS_PER_TYPE(type) (sizeof(type) * BITS_PER_BYTE) 
#define BITS_TO_LONGS(nr)    DIV_ROUND_UP(nr, BITS_PER_TYPE(long)) 
#define DECLARE_BITMAP(name,bits) unsigned long name[BITS_TO_LONGS(bits)] 
 
//定义: 
typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t; 
//使用: 
unsigned int cpumask_first(const struct cpumask *srcp) 
void cpumask_set_cpu(unsigned int cpu, struct cpumask *dstp) 
void cpumask_clear_cpu(int cpu, struct cpumask *dstp) 
int cpumask_test_cpu(int cpu, const struct cpumask *cpumask)

33. 10的次方表示

#include <stdio.h> 
 
#define POW10(exp) ((unsigned int)1e##exp) 
 
void main() 
{ 
    printf("10^2=%lf\n", 1e2); 
    printf("10^-2=%lf\n", 1e-2); 
} 
 
/* 
$ ./pp 
10^2=100.000000 
10^-2=0.010000 
*/

本文参考链接:https://www.cnblogs.com/hellokitty2/p/9070078.html
阅读延展