引言 - 为寻一颗明星
为要寻一颗明星
徐志摩 1924年12月1日《晨报六周年纪念增刊》 我骑著一匹拐腿的瞎马, 向著黑夜里加鞭;—— 向著黑夜里加鞭, 我跨著一匹拐腿的瞎马。// 我冲入这黑绵绵的昏夜, 为要寻一颗明星;—— 为要寻一颗明星, 我冲入这黑茫茫的荒野。// 累坏了,累坏了我胯下的牲口, 那明星还不出现;—— 那明星还不出现, 累坏了,累坏了马鞍上的身手。// 这回天上透出了水晶似的光明, 荒野里倒著一只牲口, 黑夜里躺著一具尸首。—— 这回天上透出了水晶似的光明!//
{ 风 : http://music.163.com/#/song?id=5276735 }
前言 - 有点扯
C基本是程序生涯的入门语言. 虽说简单, 但已经断层了. 估计是不合时宜吧.
工作中也就在网络层框架会看见部分C的影子. 自己用C开发久了, 发现C一个弊端是 当一个项目超过 2千行 x 10 时候用C协作
非常难受. C风格是个自由的英雄主义表现.
但是 真实的生活如dota, 我们不是 hero 而只是 那个小兵, 时来运转会成为超级兵. 哈哈.
但这不重要, 喜欢就好.
生活不止眼前的苟且 ... ...
好那我们开始,看看那些关于C基础的活化石. 真想问 <<C程序设计>> 这门课你真的学好了吗?
正文 - 有点难
1. int i = 0; ++i 一直继续会怎样?
我们先看这样的测试代码
#include <stdio.h> #include <stdlib.h> /* * 测试 int 的最大值 */ int main(void) { int id = 0x7fffffff; printf("-1 = %x ", -1); printf("id = %d ", id); ++id; printf("id = %d ", id); id += 0x7fffffff; printf("id = %d ", id); id += 0x7fffffff; printf("id = %d ", id); system("pause"); return 0; }
你能算明白测试结果吗, 如果可以说明你计算机组成原理学的很好. 运行截图如下
因而 我们得到 int i = 0; ++i 一直继续的 会是 0->INT_MAX->INT_MIN->0 这样循环的. 例如 skynet 存在这个使用错误
int id = __sync_add_and_fetch(&(ss->alloc_id), 1); if (id < 0) { id = __sync_and_and_fetch(&(ss->alloc_id), 0x7fffffff); }
原作者希望 再从 0开始 , 但却忘了
#define INT_MIN (-2147483647 - 1) // minimum (signed) int value #define INT_MAX 2147483647 // maximum (signed) int value
对于 signed MAX + MIN = -1 , 因为计算机中 正数从0开始, 负数从-1开始.
2. 添加双引号 的宏用法
看下面代码
// 添加双引号的宏 #ifdef _API_MEM # define STRINIFY_(S) #S # define STRINIFY(S) STRINIFY_(S) # include STRINIFY(_API_MEM) # undef STRINIFY # undef STRINIFY_ #endif
有些工程中使用上面代码, 来动态的导入头文件. 核心在于 STRINIFY_ 和 STRINIFY 两个宏使用. 我们测试一下
#include <stdio.h> #include <stdlib.h> # define STRINIFY_(S) #S # define STRINIFY(S) STRINIFY_(S) #define _API_MEM api.h // 测试添加双引号宏 int main(void) { puts(STRINIFY_(_API_MEM)); puts(STRINIFY(_API_MEM)); system("pause"); return 0; }
运行结果是
通过这个发现, 如果直接用 STRINIFY_ 不会将参数展开了. 这也是一个C行业淫荡的技巧了. 但是觉得大巧若拙
个人觉得 最好做法是
#define _API_MEM "api.h"
#ifdef _API_MEM # include _API_MEM #endif
3. 除了sizeof, 其实还有 offsetof
直接看例子
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stddef.h> #include <stdbool.h> #define UDP_ADDRESS_SIZE 19 // ipv6 128bit + port 16 bit + 1 byte type struct write_buffer { struct write_buffer* next; void* buffer; char* ptr; int sz; bool userobject; uint8_t udp_address[UDP_ADDRESS_SIZE]; }; /* * 测试 宏 offsetof */ int main(int argc, char* argv[]) { printf("offsetof(struct write_buffer, udp_address[0]) = %d ", offsetof(struct write_buffer, udp_address[0])); printf("offsetof(struct write_buffer, udp_address) = %d ", offsetof(struct write_buffer, udp_address)); system("pause"); return 0; }
运行的结果如下
通过上面 可以知道 offsetof 其实计算的是结构体中字段的偏移量. 关于结构体的内存计算基础能力, 必须要掌握的. 洞悉内存结构很重要.
其实 offsetof 是 stddef.h 中定义的一个 宏 如下
#define offsetof(s,m) ((size_t)&(((s*)0)->m))
是不是很清爽. 就是这样, 没事简单的.
其实上面代码还隐含一个 关于 数组的 细节 . int a[10]; &a[0] == a == &a 地址是相同的.
4. 如何构造一个只能在堆上分配结构体?
//堆上 声明结构体 struct request_open { int id; int port; uintptr_t opaque; char host[]; };
就是上面那样, 加了[], 表示不完全类型. 只能在堆上分配内存. 使用方法.
struct request_open *open = malloc(sizeof(struct request_open) + sizeof(char) * 19);
这种结构一般在底层库会看见. 一些老的程序员喜欢这么写
//堆上 声明结构体 struct request_open { int id; int port; uintptr_t opaque; char host[0]; };
或
//堆上 声明结构体 struct request_open { int id; int port; uintptr_t opaque; char host[1]; };
因为老的编译器不支持 char host[]; 后面标准加了. 后来没改过习惯.
5. 如何构造一个在栈上初始化的指针变量
说的不好明白, 或者这么问, 下面定义的类型怎么解.
struct cstring_data { char* cstr; //保存字符串的内容 uint32_t hash; //字符串hash,如果是栈上的保存大小 uint16_t type; //主要看 _INT_STRING_* 宏,默认0表示临时串 uint16_t ref; //引用的个数, 在 type == 0时候才有用 }; typedef struct _cstring_buffer { struct cstring_data* str; } cstring_buffer[1]; //这个cstring_buffer是一个在栈上分配的的指针类型
上面也是底层库中会遇到一个技巧.
当声明cstring_buffer cb; 后.可以直接cb->str调用它,
当 cb 传入到 函数中. 仍然可以 cb->str. 可以理解为这个值是栈上的但是可以当指针变量用法去使用. 看下面也许好理解
typedef struct _jmp_buf { int _jb[_JBLEN + 1]; } jmp_buf[1];
这个是 setjmp.h 里的一行定义,把一个 struct 定义成一个数组。
这样,在声明 jmp_buf 的时候,可以把数据分配到堆栈上。但是作为参数传递的时候则作为一个指针.
扩展一下阅读理解可以看下面. 应该可以知道为什么这么搞.
//特殊的数组 声明结构体 #define _INT_STRING_ONSTACK (4) //标识 字符串分配在栈上 //0 潜在 标识,这个字符串可以被回收,游离态 #define _INT_ONSTACK (128) //栈上内存大小 struct cstring_data { char* cstr; //保存字符串的内容 uint32_t hash; //字符串hash,如果是栈上的保存大小 uint16_t type; //主要看 _INT_STRING_* 宏,默认0表示临时串 uint16_t ref; //引用的个数, 在 type == 0时候才有用 }; typedef struct _cstring_buffer { struct cstring_data* str; } cstring_buffer[1]; //这个cstring_buffer是一个在栈上分配的的指针类型 /* * v : 是一个变量名 * * 构建一个 分配在栈上的字符串. * 对于 cstring_buffer 临时串,都需要用这个 宏声明创建声明, * 之后可以用 CSTRING_CLOSE 关闭和销毁这个变量,防止这个变量变成临时串 */ #define CSTRING_BUFFER(v) char v##_cstring[_INT_ONSTACK] = { '