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字节
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