【C语言】那些优秀代码里的骚操作(持续更新…)
- 1、联合体`union`的妙用
- 2、`#include`的本质是什么?
- 3、脱裤子放屁的`do{ }while(0)`
- 4、一个成熟的代码要学会自己写函数
- 5、……
语言这个东西,其实没有奇技淫巧,凡是可以写出来的,能被编译器识别的,都是常规操作,这里所谓的骚操作,其实是想说那些看起来不合理,但是自有道理的写法。这些写法大都是对C语言本质理解不透彻导致的惊奇。然而,我们应该保持惊奇, 纪德说:“智者,乃是对一切都发生惊奇的人。”
很多奇怪的写法,大多是我看一些SDK、库、框架时见到过的,这些操作也许你见到过,也许会说:哦,就这?那么恭喜你,这个逼我不装了,让你来。
这篇文章,想起来就就会来更新一下……
1、联合体union
的妙用
假设有以下字节流数据date
(共6Byte长度):
字节 | 1Byte | 1Byte | 1Byte | 1Byte | 1Byte | 1Byte |
---|---|---|---|---|---|---|
含义 | 年 | 月 | 日 | 时 | 分 | 秒 |
如果我们要分别解析出每一字节,我们一般会采用移位操作,但代码复杂
如果我们这样写:
typedef struct time_s {char year;char mouth;char day;char hour;char min;char sec;
}time_t;union time {time_t t;char date[6];
};void get_time() {union time time_u;//这个例子有点像银行存钱时 整存零取 的模式memcpy(&time_u.date, "12345",6);//整存printf("the hour is:%c", time_u.t.hour);//零取
}
则将输出
the hour is:4
这就充分利用了联合体共享内存的特性。
2、#include
的本质是什么?
看到这个题目,有人会说:当然是包含头文件
不,这只是一个现象,不是本质,它的本质是展开所包含文件的代码。
这是有一天看LWIP源码恍然大悟的:
typedef enum
{
#include LWIP_MEMPOOL(name,num,size,desc) MEMP_##name,
#include "lwip/priv/memp_std.h"MEMP_MAX
} memp_t;
这段代码经过编译器处理后是这样的:
typedef enum
{ MEMP_RAW_PCB,MEMP_UDP_PCB,MEMP_TCP_PCB,MEMP_MAX
}
那memp_std.h
中放了什么呢?
LWIP_MEMPOOL(RAW, MEMP_NUM_RAW_PCB ,sizeof(struct raw_pcb), "raw_pcb")
LWIP_MEMPOOL(UDP, MEMP_NUM_UDP_PCB ,sizeof(struct udp_pcb), "udp_pcb")
LWIP_MEMPOOL(TCP, MEMP_NUM_TCP_PCB ,sizeof(struct tcp_pcb), "tcp_pcb")
所以,头文件里放的东西不一定是一些变量、函数的声明,只要把它展开,放在被引用处的上下文能编译通过,那就可以随便你写什么!那么我们倒过来想:是不是我们也可以把原来.c中的一段代码截取出来放到.h中,然后在此处用#inlcude ".h"
取而代之?当然可以!那么这么做有什么好处呢?比如说,我们想把.c的一段代码单独拎出来提供给其他层使用,当然了,大部分时候,这些内容是那些我们常见的函数和变量,但是记住:那些.h中未声明的东西我们依然可以在.h中使用,因为它们可能在.c中展开之后就语义通顺了。
另外注意,#include不是.h的专属,也可以是txt。
3、脱裤子放屁的do{ }while(0)
上一次看到do{ }while(0)
还是在上一次,它的一个主要作用就是限制作用域,这也是C语言中{}
的作用。有些人喜欢拿宏展开举例子,来证明do{ }while(0)
可以限制代码的作用域,避免宏展开后与上下文结合产生新的语义,但这{}
也可以做到。
①先来说说这个限制作用域的问题:
void print()
{cout<<"print: "<<endl;
}
void send()
{cout <<"send: "<<endl;
}#define LOG print();send();int main(){if (false)LOGsystem("pause");return 0;
}
这种情况这样写:#define LOG { print();send(); }
,也可以解决啊,do{ }while(0)
纯粹是脱裤子放屁
但是如果这样调用宏:
if (false)LOG;
else{
}
使用{}
来限制作用域,展开后则为:
if (false)
{print();send();
};
else{
}
便会报错。
使用do{ }while(0)
来限制作用域,展开后则为:
if (false)
do{print();send();
}while(0);
else{
}
这才是do{ }while(0)
的优势。
②还有种说法,do{ }while(0)
还可以和break
搭配使用,以代替goto
语句,优点就是break即使漏写,也会跳出执行后面的语句;goto END;
漏写便执行不到END:
后的语句,我认为是无稽之谈,这也漏写,那也漏写,别编程了。这和①情况完全不同,①中的宏可能是由其他层提供的,层间存在不确定性,goto不存在这个问题。可以这样写,但别说什么代替goto,goto没惹你,告诉你特性了,你自己不注意是你的问题。这仅代表我此时码字的心情,明天我可能就不这么说了,不喜勿喷。
4、一个成熟的代码要学会自己写函数
这是一段来自某厂商SDK的shell交互代码,用宏来生成函数,有C基础的人一看便知:
#define CLI_CMD(name, help, func) \int _cli_inter_##func (char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) \{ \int argc = 0; \char *argv[CLI_MAX_ARGS]; \argc = CLITokenizeCommand(pcCommandString, argv); \return func(argc, argv); \} \__attribute__((__used__)) const CLI_Command_Definition_t _cli_cmd_##func __SECTION(".commands") = \{ name, help, _cli_inter_##func }
但不要忘了使用__attribute__((__used__))
,向编译器说明这段代码有用,即使在没有用到的情况下编译器也不会警告!