C语言的奇技淫巧(1-50)

Posted 嵌入式软件实战派

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言的奇技淫巧(1-50)相关的知识,希望对你有一定的参考价值。

关注公众号“嵌入式软件实战派”,获得更多C/C++技巧。

01. 宏定义用do{}while(0)

如果定义的宏函数后面有多条语句,使用这样的方式会有问题:

#define FUNC()  func1(); func2()
if(bRunF)
    FUNC();

展开宏定义后会变成:

if(bRunF)
    func1();
    func2();

逻辑就不对了。可以用这一的方式解决,非常好用:

#define FUNC()  do{func1(); func2();}while(0)
02. 数组的初始化

假如给arr的第2~6元素初始化为5,也许你会

int arr[10] = {0, 5, 5, 5, 5, 5, 0, 0, 0, 0};

现在告诉你C99可以这样:

int arr[10] = {[1... 5] = 5};
03. 数组的访问

你想取数组的第6个元素(下标为5),教科书教你这样做:

int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int n1 = arr[5];
int n2 = *(arr+5);

其实你可以:

int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int n = 5[arr];

也不会有错,实际上
arr[5]对应*(arr+5),而5[arr]对应*(5+arr),没多大区别。

04. 结构体的初始化

结构体的初始化,传统的做法是:

typedef struct
{
    int a;
    int x;
    int y;
    int z;
    char b;
    short c;
}S;
S s = {100, 0, 0, 0, 'A', 0x12);

对于C99,其实你可以:

typedef struct
{
    int a;
    int x;
    int y;
    int z;
    char b;
    short c;
}S;
S s = {
            .a = 100, 
            .b = 'A', 
            .c = 0x12
        };
05. 用include的方式初始化大数组
double array[SIZE][SIZE] = {
    #include "float_values.txt"
}
06. Debug时输出文件名、函数名、行号等
#define DEBUG_INFO() fprintf(stderr,"[DEBUG]%s:%d %s\\n", __FILE__, __LINE__, __FUNCTION__);
07. C语言有-->“趋向于…”操作符?
int main(void)
{
        int n = 10; 
        while(n --> 0 ) // n goes to 0
        { 
                printf("%d ", n);
        }
        printf("\\n");
}

实际上C语言没有这个-->操作符,是-->的组合而已

        while( n--  >  0 )
08. 获得任意类型数组的元素数目
#define NUM_OF(arr) (sizeof (arr) / sizeof (*arr))
09. 判断运行环境的大小端

Linux有以下代码:

    static union { 
        char c[4]; 
        unsigned long l; 
    } endian_test = { { 'l', '?', '?', 'b' } };
    #define ENDIANNESS ((char)endian_test.l)

    printf("ENDIANNESS: %c\\n", ENDIANNESS);
10. 编译时做条件检查

Linux Kernel有以下代码

/* Force a compilation error if condition is true */
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

例如,在某些平台为了防止内存对齐问题,检查一个结构体或者一个数组的大小是否为8的倍数。

BUILD_BUG_ON((sizeof(struct mystruct) % 8) != 0);

除了这个,还有

#define BUILD_BUG_ON_ZERO(e)  (sizeof(struct{int : -!!(e);}))
#define BUILD_BUG_ON_NULL(e)  ((void*)sizeof(struct{int : -!!(e);}))
#define BUILD_BUG_ON(condition)  ((void)BUILD_BUG_ON_ZERO(condition))
#define MAYBE_BUILD_BUG_ON(condition)  ((void)sizeof(char[1 - 2 * !!(condition)]))
11. 用异或运算实现数据交换

交换俩变量数据,一般做法是:

// 方法1
temp = a;
a = b;
b = temp;

// 方法2
a=a+b;
b=a-b;
a=a-b;

方法1需要第三个变量,方法二存在数据溢出可能,可以尝试下以下方法:

a = a ^ b;
b = a ^ b;
a = a ^ b;
12. 判断语句中把const数值放在前面

通常条件语句写成

if(n == 0){ /*...*/ }

但是,有可能手误写成

if(n = 0){ /*...*/ }

这种错误只有机器在运行时候知道,而人不一定能发现这种bug。
把数值放在前面就不怕了,==写成=,编译器就知道

if(0 == n){ /*...*/ }
13. 用冒号表达式替代if...else...语句

这个用法应该很普遍了,不算什么特别的技巧了。

if(y < 0)
{
    x = 10;
}
else
{
    x = 20;
}

可以改成以下一行代码即可

x = (y < 0) ? 10 : 20;
14. 判断一个整数是否为2的幂

也许你会不断地将这个数除以2,除到底,然而Linux kernel有个巧妙的办法:

#define is_power_of_2(n) ((n) != 0 && ((n) & ((n) - 1)) == 0)

((n) & ((n) - 1)) == 0这个不理解?那先想想2的X次方的值的二进制是怎样的。

15. 静态链表

直接看代码

    struct mylist {
        int a;
        struct mylist* next;
    };
    #define cons(x, y) (struct mylist[]){{x, y}}
    struct mylist *list = cons(1, cons(2, cons(3, NULL)));
    struct mylist *p = list;
    while(p != 0) {
        printf("%d\\n", p->a);
        p = p -> next;
    };
16. 柔性数组
#include <stdlib.h>
#include <string.h>

struct line
{
    int length;
    char contents[0];
};
struct line *thisline = (struct line *) malloc (sizeof (struct line) + this_length);
thisline->length = this_length;

struct f1 { int x; int y[]; } f1 = { 1, { 2, 3, 4 } }; 
struct f2 { struct f1 f1; int data[3]; } f2 = { { 1 }, { 2, 3, 4 } };

详见6.18 Arrays of Length Zero

17. 数组之间直接赋值
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int b[10] = {0};
b = a;

这样是非法的,但是你可以放数组穿个马甲:

typedef struct
{
    int n[10];
}S;
S a = {{0,1,2,3,4,5,6,7,8,9}};
S b = {0};
b = a;
18. #include的不一定是要.h文件

#include后面跟的可以是任意后缀的,但文件内容一定要是合法的。例如

#include "test.fxxk"
19. 自动获取变量类型
#define var(left, right) __typeof__(right) left = (right)

var(s, 1LL); // 相当于 long long s = 1LL;

是不是有点像C++ 11的auto类型?

20. 宏定义函数MIN(x,y)的终极做法
#define MIN(x, y)   x < y? x : y    // 这样给0分

#define MIN(x, y)   (x < y? x : y)  // 这样给50分
// 不信你试试这个
int n = 3 * MIN(3, 4 < 5 ? 4 : 5);

#define MIN(x, y)   ((x) < (y)? (x) : (y))  // 这个给90分
// 不信你试试这个
double xx = 1.0;
double yy = MIN(xx++, 1.5);
printf("xx=%f, yy=%f\\n",xx,yy);

// 以下放大招了,看看GNU的
#define MIN(A,B)	({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
double xx = 1.0;
double yy = MIN(xx++, 1.5);
printf("xx=%f, yy=%f\\n",xx,yy);
21. 行控制#line

也许你知道用__LINE__可以输出行号,然而你试下这个:

 #line 12345 "abcdefg.xxxxx"    
 printf("%s line: %d\\n", __FILE__, __LINE__);    printf("%s line: %d\\n", __FILE__, __LINE__);

不单止行号被改了,文件名也被改了,是不是我们可以用这个干点啥……想想?

22. C和C++代码混合编译

在C的头文件上面

#ifdef __cplusplus
extern "C" {
#endif

然后再头文件下面

#ifdef __cplusplus
}
#endif
23. 用查表法实现hex2str

直接上代码

void hex2str(const unsigned char* hex, int size, char* str)
{
    char char_arr[17] = "0123456789ABCDEF";
    for(int i = 0; i < size; i++)
    {
        str[3*i] = char_arr[hex[i]>>4];
        str[3*i+1] = char_arr[hex[i]&0x0F];
        str[3*i+2] = ' ';
    }
}
24. 用sprintf实现hex2str

直接上代码

void hex2str(const unsigned char* hex, int size, char* str)
{
    for(int i = 0; i < size; i++)
    {
        sprintf(&str[3*i], "%02X ", hex[i]);
    }    
}
25. 将变量名变字符串

如果想打印一个变量名和它的值,也许会这样:

unsigned int program_flag = 0xAABBCCDD;
printf("program_flag: 0x%08X\\n", program_flag);

对于你有很多这样的变量要打印,建议你做个宏函数:

#define PRINT_HEX_VAR(var)  printf("%s: 0x%08X\\n", #var, var);
unsigned int program_flag = 0xAABBCCDD;
PRINT_HEX_VAR(program_flag);
26. 获取结构体元素的偏移
#define offsetof(type, member) ( (size_t)&((type*)0->menber) )

typedef struct
{
    char a;
    int b;
 }S;
 offsetof(S, b);
27. 根据结构体成员获取结构体变量指针
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:        the pointer to the member.
 * @type:       the type of the container struct this is embedded in.
 * @member:     the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({                      \\
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \\ 
    (type *)( (char *)__mptr - offsetof(type,member) );}) 

这个怎么玩?看看链表

struct list_head {
    struct list_head *next;
    struct list_head  *prev;
};

struct ipstore{
    unsigned long time;
    __u32 addr[4];
    struct list_head list;
};

container_of(ist1->list, struct ipstore, list)
28. scanf高级玩法
scanf(%[^,], a); // This doesn’t scrap the comma
scanf(%[^,],,a); // This one scraps the comma
scanf(%[^\\n]\\n”, a); // It will read until you meet  '\\n', then trashes the '\\n'
scanf(%*s %s”, last_name); // last_name is a variable

这是啥意思,正则表达式先了解下?然后自己试试,理解会更深入。

29. 两个数相加可以不用+号?
int Add(int x, int y)
{
      if (y == 0)
            return x;
      else
            return Add( x ^ y, (x & y) << 1);
}
30. 调试的时候打印数组

你是不是曾经为打印数组而烦恼,每次都要将元素一个个取出来?

#define ARR_SIZE(arr)               (sizeof(arr)/sizeof(*arr))
#define PRINT_DIGIT_ARR(arr)    do{\\
                                               printf("%s: ", #arr); \\
                                               for(int i=0; i < ARR_SIZE(arr); i++) \\
                                                   printf("%d ", arr[i]);\\
                                               printf("\\n");\\
                                            }while(0)
                                
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
PRINT_DIGIT_ARR(arr);
31. 感受下这个0x5F3759DF
float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck?
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //      y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed
 
    return y;
}
32. switch-case的特殊玩法

直接看代码

void send(uint8* to, uint8 from, uint16 count)
{
    uint16 n = (count + 7) / 8; 
    switch (count % 8) 
    { 
        case 0: do { *to = *from++; 
        case 7: *to = *from++; 
        case 6: *to = *from++; 
        case 5: *to = *from++; 
        case 4: *to = *from++; 
        case 3: *to = *from++; 
        case 2: *to = *from++; 
        case 1: *to = *from++; 
    } while (--n > 0); 
}

实际上它是

void send(uint8* to, uint8 from, uint16 count)
{
    do 
    { 
        *to = *from++; 
    } while (--count > 0); 
}

使用最上面的switch-case的形式大大提高了运行效率。
理解不了?汇编看看。
还是理解不了?那就网上自行搜索“Duff’s Device”

33. 防止头文件重复包含导致问题

这个用法很常见了,而且非常有用

// xxx.h
#ifndef _XXX_H_
#define _XXX_H_

//  Header file contents...

#endif

当然,如果你的编译器支持的话,也可以

// xxx.h
#pragma once

//  Header file contents...

不过为了更好的兼容性,我建议你用第一种方法。

34. 2的N次幂ROUNDUP
#define ROUNDUP(a, size) (((a) & ((size)-1)) ? (1+((a) | ((size)-1))) : (a))

其中,size是2的整数次幂,而

  1. a & (2^n-1)检查a的低位是否有值
  2. a | (2^n - 1) 将a的低n位赋值为1
  3. 1 + a | (2^n -1) 为a最近的下一个2^n倍值
ROUNDUP(10, 8);     // 结果为16
ROUNDUP(10, 16);   // 结果为16
ROUNDUP(10, 32);   // 结果为32
ROUNDUP(16, 16);   // 结果为16

有什么用?申请内存的时候可以按某字节对齐,减少内存碎片啊。

35. 某整数的ROUNDUP
#define VAL_ROUNDUP(size, val_size)     (((size)+val_size-1)/val_size*val_size)

这个不是按2的次幂ROUNDUP的,而是按某个整数的倍数ROUNDUP,例如

VA

以上是关于C语言的奇技淫巧(1-50)的主要内容,如果未能解决你的问题,请参考以下文章

RUST 语言特性之所有权

C的奇技淫巧

acm竞赛中有啥奇技淫巧 进制转换

JavaScript奇技淫巧44招

JavaScript奇技淫巧44招

Hadoop的奇技淫巧