C基础 那些年用过的奇巧淫技
引言 - 为寻一颗明星
为要寻一颗明星
徐志摩 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\n", -);
printf("id = %d\n", id);
++id;
printf("id = %d\n", id);
id += 0x7fffffff;
printf("id = %d\n", id);
id += 0x7fffffff;
printf("id = %d\n", id); system("pause");
return ;
}
你能算明白测试结果吗, 如果可以说明你计算机组成原理学的很好. 运行截图如下
因而 我们得到 int i = 0; ++i 一直继续的 会是 0->INT_MAX->INT_MIN->0 这样循环的. 例如 skynet 存在这个使用错误
int id = __sync_add_and_fetch(&(ss->alloc_id), );
if (id < ) {
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 ;
}
运行结果是
通过这个发现, 如果直接用 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\n", offsetof(struct write_buffer, udp_address[]));
printf("offsetof(struct write_buffer, udp_address) = %d\n", offsetof(struct write_buffer, udp_address)); system("pause");
return ;
}
运行的结果如下
通过上面 可以知道 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) * );
这种结构一般在底层库会看见. 一些老的程序员喜欢这么写
//堆上 声明结构体
struct request_open {
int id;
int port;
uintptr_t opaque;
char host[];
};
或
//堆上 声明结构体
struct request_open {
int id;
int port;
uintptr_t opaque;
char host[];
};
因为老的编译器不支持 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[]; //这个cstring_buffer是一个在栈上分配的的指针类型
上面也是底层库中会遇到一个技巧.
当声明cstring_buffer cb; 后.可以直接cb->str调用它,
当 cb 传入到 函数中. 仍然可以 cb->str. 可以理解为这个值是栈上的但是可以当指针变量用法去使用. 看下面也许好理解
typedef struct _jmp_buf {
int _jb[_JBLEN + ];
} jmp_buf[];
这个是 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[]; //这个cstring_buffer是一个在栈上分配的的指针类型 /*
* v : 是一个变量名
*
* 构建一个 分配在栈上的字符串.
* 对于 cstring_buffer 临时串,都需要用这个 宏声明创建声明,
* 之后可以用 CSTRING_CLOSE 关闭和销毁这个变量,防止这个变量变成临时串
*/
#define CSTRING_BUFFER(v) \
char v##_cstring[_INT_ONSTACK] = { '\0' }; \
struct cstring_data v##_cstring_data = { v##_cstring, , _INT_STRING_ONSTACK, }; \
cstring_buffer v; \
v->str = &v##_cstring_data;
6. 那些年总有个align字段进行内存对齐
/*字节对齐的类型Align,为了优化CPU读取*/
typedef union {
long l_dummy;
double d_dummy;
void *p_dummy;
} Align; /*标志大小,默认是4字节*/
#define MARK_SIZE (4)
/*内存块头结点,双向链表结点size,filename,line都是为了调试添加的调试信息.prev和next是双向链表的核心*/
typedef struct {
int size;
char *filename;
int line;
Header *prev;
Header *next;
unsigned char mark[MARK_SIZE];
} HeaderStruct; /*Align类型的字节大小*/
#define ALIGN_SIZE (sizeof(Align))
/*这是个不错的技巧,求最小的n使得n*ALIGN_SIZE>=val成立,n,val,ALIGN_SIZE都属于自然数*/
#define revalue_up_align(val) ((val) ? (((val) - 1) / ALIGN_SIZE + 1) : 0)
/*将HeaderStruct按照Align划分,找到最小的n,使得n*ALIGN_SIZE>=sizeof(HeaderStruct),在自然数集中*/
#define HEADER_ALIGN_SIZE (revalue_up_align(sizeof(HeaderStruct))) /*实现了memory.h接口中Header不完全类型,Align是对齐用的,内存结构的头结点.链表链接的主要结点*/
union Header_tag {
HeaderStruct s;
Align u[HEADER_ALIGN_SIZE];
};
主要看 union Headr_tag 中 Align结构. 保证不同机器上内存是对齐的. 比较古老了. 特别底层的库会见到.
7. 可变参数宏, 那些事
同样直接看下面工程中用的示例
//4.0 控制台打印错误信息, fmt必须是双引号括起来的宏
#ifndef CERR
#define CERR(fmt, ...) \
fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
__FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__)
#endif/* !CERR */ //4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量
#ifndef CERR_EXIT
#define CERR_EXIT(fmt,...) \
CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE)
#endif/* !ERR */ #ifndef IF_CERR
/*
*4.2 if 的 代码检测
*
* 举例:
* IF_CERR(fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), "socket create error!");
* 遇到问题打印日志直接退出,可以认为是一种简单模板
* code : 要检测的代码
* fmt : 必须是""括起来的字符串宏
* ... : 后面的参数,参照printf
*/
#define IF_CERR(code, fmt, ...) \
if((code) < ) \
CERR_EXIT(fmt, ##__VA_ARGS__)
#endif //!IF_CERR #ifndef IF_CHECK
/*
* 是上面IF_CERR 的简化版很好用
*/
#define IF_CHECK(code) \
if((code) < ) \
CERR_EXIT(#code)
#endif // !IF_CHECK
那 传说中的 3颗痣, 就是可变参数宏的一切o(∩_∩)o
8. 简单的谢幕. 还是宏
一个数如何和0比较,真的是 == 吗. 其实好的思路是定义阀值.
//3.0 浮点数据判断宏帮助, __开头表示不希望你使用的宏
#define __DIFF(x, y) ((x)-(y)) //两个表达式做差宏
#define __IF_X(x, z) ((x)<z&&(x)>-z) //判断宏,z必须是宏常量
#define EQ(x, y, c) EQ_ZERO(__DIFF(x,y), c) //判断x和y是否在误差范围内相等 //3.1 float判断定义的宏
#define _FLOAT_ZERO (0.000001f) //float 0的误差判断值
#define EQ_FLOAT_ZERO(x) __IF_X(x,_FLOAT_ZERO) //float 判断x是否为零是返回true
#define EQ_FLOAT(x, y) EQ(x, y, _FLOAT_ZERO) //判断表达式x与y是否相等
谢幕吧 : [
老师布置一个作业, 问学生, 看见那个晾衣杆吗. 谁能帮我测试出高度来.
一个同学自告奋勇的把晾衣杆放倒了. 测试出长度 为 1.5m.
老师把他骂了一顿, 我要的是高度, 不是长度.
]
//5.0 获取数组长度,只能是数组类型或""字符串常量,后者包含'\0'
#ifndef LEN
#define LEN(arr) \
(sizeof(arr)/sizeof(*(arr)))
#endif/* !ARRLEN */
后记 - 认真做容易的
错误是难免的, 欢迎吐槽交流. ... 还有下半辈子的苟且. 哈哈, 但是在变化, 那会变得有意思, O(∩_∩)O哈哈~
C基础 那些年用过的奇巧淫技的更多相关文章
- LLDB奇巧淫技
打印视图层级 这个相信很多人都会了,是ta是ta就是ta recursiveDescription 用法大概就是如下 123 po [self.view recursiveDescription] p ...
- iOS开发的一些奇巧淫技(转载)
iOS开发的一些奇巧淫技 http://www.cocoachina.com/ios/20141229/10783.html iOS开发的一些奇巧淫技2 http://www.cocoachina.c ...
- octave之奇巧淫技向量化计算实现寻找样本点所属聚类下标
前面有文章提到过,K-means算法,第一步骤是找出样本点的的所属聚类.下面用两种方式实现,一种是普通的循环,一种是完全向量化计算. 假设 : X 是m×n样本矩阵,其每一行是一个样本,m表示样本数目 ...
- iOS开发的一些奇巧淫技
TableView不显示没内容的Cell怎么办? 类似这种,我不想让下面那些空的显示. 很简单. self.tableView.tableFooterView = [[UIView alloc] in ...
- 介绍一个C++奇巧淫技
你能实现这样一个函数吗: MyType type; HisType htype; serialize_3(11, type, htype); serialize_4(type, hty ...
- iOS开发的一些奇巧淫技2
能不能只用一个pan手势来代替UISwipegesture的各个方向? - (void)pan:(UIPanGestureRecognizer *)sender { typedef NS_ENUM(N ...
- [异常解决] 奇巧淫技——VirtualBox中的linux无显示启动,并在win7上远程控制
楼主是资深技术宅(癖),由于感觉手上的老笔记本太卡,遂狠心买了个性能至强的主机同时配了个投影仪(满足躺着打代码的意淫场景).但是体验了大概一个月发现还是坐着打代码舒服,但是如下图坐着打代码总是要抬头看 ...
- Windows的奇巧淫技(为什么GIF显示不出来??)
谁的电脑里没点小秘密?东藏西藏到最后自己都找不到了有木有?今天教大家个隐藏文件的高招: 将任意文件隐藏到图片中!怎么样?再也不用建什么「马列主义哲学」的文件夹啦!
- Python学习之路:一天搞定基础部分
~代表省略的内容,如变量名.字符串等等 1.Pyhton中比较特别的运算: **:代表指数运算,例如2**3 = 8 //:代表整除运算,这一点和Java不同 2.Python的注释: #:单行注释 ...
随机推荐
- leetcode题目总结(转)
https://www.douban.com/note/330562764/ http://blog.csdn.net/lanxu_yy/article/details/17848219 ht ...
- EndNote文献管理
一直想写个博客,但是一直没有好好坐下来对自己工作进行一个梳理.从今天开始吧,争取多写一点. 今天,先介绍一下科技论文写作中经常使用的一款软件EndNote,这个软件,掌握它的使用方法后会觉得很方便:但 ...
- 浅析人脸检测之Haar分类器方法
一.Haar分类器的前世今生 人脸检测属于计算机视觉的范畴,早期人们的主要研究方向是人脸识别,即根据人脸来识别人物的身份,后来在复杂背景下的人脸检测需求越来越大,人脸检测也逐渐作为一个单独的研究方向发 ...
- Android基础总结(8)——服务
服务(Service)是Android中实现程序后台运行的解决方案,它非常适合用于去执行哪些不需要和用户交互而且还要长期运行的任务.服务的运行不依赖任何用户界面,即使当程序被切换到后台,或者用户打开了 ...
- 华为OJ平台——字符串分隔
题目描述: 连续输入字符串,请按长度为8拆分每个字符创 后输出到新的字符串数组: 长度不是8整数倍的字符串请在后面补数字0,空字符串不处理 输入 连续输入字符串(输入两次,每个字符长长度小于100)输 ...
- Iphone5S 体验(视频+截图)
Iphone5S眨眼从外观看和5区别不大,仔细一看后面,最大的变化还是闪光灯,内部使用了A7的处理器运算速度增强了不少.无论照相还是FaceTime摄像都非常清晰,就连常用的手电筒和动态天气预报都考虑 ...
- [前端 3]纯Js制作俄罗斯方块游戏
导读:在别人文章里看到了,然后写了一遍.结果出错了,然后调出来了,然后理解了一下,加了点注释,有一些想法.忘了在 哪一篇上面看的了,就贴不出来链接地址.原谅.呃,真没自己的东西,权当练打字了吧.其实, ...
- Python 编程规范-----转载
Python编程规范及性能优化 Ptyhon编程规范 编码 所有的 Python 脚本文件都应在文件头标上 # -*- coding:utf-8 -*- .设置编辑器,默认保存为 utf-8 格式. ...
- CSS3 Filter
Filters主要是运用在图片上,以实现一些特效.(尽管他们也能运用于video上),不过我们在些只来讨论图片上的运用. 语法: elm { filter: none | <filter-fun ...
- leetcode 104
104. Maximum Depth of Binary Tree Given a binary tree, find its maximum depth. The maximum depth is ...