[牛人杂谈] C基础 那些年用过的奇巧淫技

[复制链接]
2321|16
 楼主| zhuomuniao110 发表于 2016-4-30 21:18 | 显示全部楼层 |阅读模式
  1. 为要寻一颗明星
  2.         徐志摩 1924年12月1日《晨报六周年纪念增刊》

  3. 我骑著一匹拐腿的瞎马,

  4. 向著黑夜里加鞭;——

  5. 向著黑夜里加鞭,

  6. 我跨著一匹拐腿的瞎马。//

  7. 我冲入这黑绵绵的昏夜,

  8. 为要寻一颗明星;——

  9. 为要寻一颗明星,

  10. 我冲入这黑茫茫的荒野。//

  11. 累坏了,累坏了我胯下的牲口,

  12. 那明星还不出现;——

  13. 那明星还不出现,

  14. 累坏了,累坏了马鞍上的身手。//

  15. 这回天上透出了水晶似的光明,

  16. 荒野里倒著一只牲口,

  17. 黑夜里躺著一具尸首。——

  18. 这回天上透出了水晶似的光明!//
前言 - 有点扯
  C基本是程序生涯的入门语言. 虽说简单, 但已经断层了. 估计是不合时宜吧.
工作中也就在网络层框架会看见部分C的影子. 自己用C开发久了, 发现C一个弊端是 当一个项目超过 2千行 x 10 时候用C协作
非常难受. C风格是个自由的英雄主义表现.
但是真实的生活如dota, 我们不是 hero 而只是 那个小兵, 时来运转会成为超级兵. 哈哈.
但这不重要, 喜欢就好.
  生活不止眼前的苟且 ... ...
  好那我们开始,看看那些关于C基础的活化石. 真想问 <<C程序设计>> 这门课你真的学好了吗?

正文 - 有点难
1.  int i = 0; ++i 一直继续会怎样?
我们先看这样的测试代码
  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. /*
  4. * 测试 int 的最大值
  5. */
  6. int main(void) {
  7.     int id = 0x7fffffff;

  8.     printf("-1 = %x\n", -1);
  9.     printf("id = %d\n", id);
  10.     ++id;
  11.     printf("id = %d\n", id);
  12.     id += 0x7fffffff;
  13.     printf("id = %d\n", id);
  14.     id += 0x7fffffff;
  15.     printf("id = %d\n", id);

  16.     system("pause");
  17.     return 0;
  18. }


评分

参与人数 1威望 +3 收起 理由
``` + 3 一貼看完,大家都是老司機,哈哈!.

查看全部评分

 楼主| zhuomuniao110 发表于 2016-4-30 21:19 | 显示全部楼层
你能算明白测试结果吗, 如果可以说明你计算机组成原理学的很好. 运行截图如下

因而 我们得到 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开始.


 楼主| zhuomuniao110 发表于 2016-4-30 21:20 | 显示全部楼层
添加双引号 的宏用法
看下面代码
  1. // 添加双引号的宏
  2. #ifdef _API_MEM
  3. #   define  STRINIFY_(S)    #S
  4. #   define  STRINIFY(S)     STRINIFY_(S)
  5. #   include STRINIFY(_API_MEM)
  6. #   undef   STRINIFY
  7. #   undef   STRINIFY_
  8. #endif
有些工程中使用上面代码, 来动态的导入头文件. 核心在于 STRINIFY_ 和 STRINIFY 两个宏使用. 我们测试一下
  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. #   define  STRINIFY_(S)    #S
  4. #   define  STRINIFY(S)     STRINIFY_(S)

  5. #define _API_MEM api.h

  6. // 测试添加双引号宏
  7. int main(void) {

  8.     puts(STRINIFY_(_API_MEM));
  9.     puts(STRINIFY(_API_MEM));

  10.     system("pause");
  11.     return 0;
  12. }
运行结果是

通过这个发现, 如果直接用 STRINIFY_ 不会将参数展开了. 这也是一个C行业**的技巧了. 但是觉得大巧若拙
个人觉得 最好做法是
  1. #define _API_MEM "api.h"

  2. #ifdef _API_MEM
  3. #    include _API_MEM
  4. #endif


 楼主| zhuomuniao110 发表于 2016-4-30 21:22 | 显示全部楼层
除了sizeof, 其实还有 offsetof
直接看例子
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <stdint.h>
  4. #include <stddef.h>
  5. #include <stdbool.h>

  6. #define UDP_ADDRESS_SIZE 19 // ipv6 128bit + port 16 bit + 1 byte type

  7. struct write_buffer {
  8.     struct write_buffer* next;
  9.     void* buffer;
  10.     char* ptr;
  11.     int sz;
  12.     bool userobject;
  13.     uint8_t udp_address[UDP_ADDRESS_SIZE];
  14. };

  15. /*
  16. * 测试 宏 offsetof
  17. */
  18. int main(int argc, char* argv[]) {

  19.     printf("offsetof(struct write_buffer, udp_address[0]) = %d\n", offsetof(struct write_buffer, udp_address[0]));
  20.     printf("offsetof(struct write_buffer, udp_address) = %d\n", offsetof(struct write_buffer, udp_address));

  21.     system("pause");
  22.     return 0;
  23. }
运行的结果如下

通过上面 可以知道 offsetof 其实计算的是结构体中字段的偏移量. 关于结构体的内存计算基础能力, 必须要掌握的. 洞悉内存结构很重要.
其实 offsetof 是 stddef.h 中定义的一个 宏 如下
#define offsetof(s,m) ((size_t)&(((s*)0)->m))是不是很清爽. 就是这样, 没事简单的.
其实上面代码还隐含一个 关于 数组的 细节 . int a[10];  &a[0] == a == &a 地址是相同的.

 楼主| zhuomuniao110 发表于 2016-4-30 21:23 | 显示全部楼层
如何构造一个只能在堆上分配结构体?
  1. //堆上 声明结构体
  2. struct request_open {
  3.     int id;
  4.     int port;
  5.     uintptr_t opaque;
  6.     char host[];
  7. };
就是上面那样, 加了[], 表示不完全类型. 只能在堆上分配内存. 使用方法.
struct request_open *open = malloc(sizeof(struct request_open) + sizeof(char) * 19);这种结构一般在底层库会看见. 一些老的程序员喜欢这么写
  1. //堆上 声明结构体
  2. struct request_open {
  3.     int id;
  4.     int port;
  5.     uintptr_t opaque;
  6.     char host[0];
  7. };

  1. //堆上 声明结构体
  2. struct request_open {
  3.     int id;
  4.     int port;
  5.     uintptr_t opaque;
  6.     char host[1];
  7. };
因为老的编译器不支持 char host[]; 后面标准加了. 后来没改过习惯.
 楼主| zhuomuniao110 发表于 2016-4-30 21:25 | 显示全部楼层
如何构造一个在栈上初始化的指针变量
说的不好明白,或者这么问, 下面定义的类型怎么解.
  1. struct cstring_data {
  2.     char* cstr;                                 //保存字符串的内容
  3.     uint32_t hash;                                //字符串hash,如果是栈上的保存大小
  4.     uint16_t type;                                //主要看 _INT_STRING_* 宏,默认0表示临时串
  5.     uint16_t ref;                                //引用的个数, 在 type == 0时候才有用<span style="line-height: 1.5;">扩展一下阅读理解可以看下面. 应该可以知道为什么这么搞.</span>
  6. };

  7. typedef struct _cstring_buffer {
  8.     struct cstring_data* str;
  9. } 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 的时候,可以把数据分配到堆栈上。但是作为参数传递的时候则作为一个指针.
扩展一下阅读理解可以看下面. 应该可以知道为什么这么搞.
  1. //特殊的数组 声明结构体
  2. #define _INT_STRING_ONSTACK        (4)                //标识 字符串分配在栈上
  3.                                                 //0 潜在 标识,这个字符串可以被回收,游离态

  4. #define _INT_ONSTACK            (128)            //栈上内存大小

  5. struct cstring_data {
  6.     char* cstr;                                 //保存字符串的内容
  7.     uint32_t hash;                                //字符串hash,如果是栈上的保存大小
  8.     uint16_t type;                                //主要看 _INT_STRING_* 宏,默认0表示临时串
  9.     uint16_t ref;                                //引用的个数, 在 type == 0时候才有用
  10. };

  11. typedef struct _cstring_buffer {
  12.     struct cstring_data* str;
  13. } cstring_buffer[1];                            //这个cstring_buffer是一个在栈上分配的的指针类型

  14. /*
  15. * v : 是一个变量名
  16. *
  17. * 构建一个 分配在栈上的字符串.
  18. * 对于 cstring_buffer 临时串,都需要用这个 宏声明创建声明,
  19. * 之后可以用 CSTRING_CLOSE 关闭和销毁这个变量,防止这个变量变成临时串
  20. */
  21. #define CSTRING_BUFFER(v) \
  22.     char v##_cstring[_INT_ONSTACK] = { '\0' }; \
  23.     struct cstring_data v##_cstring_data = { v##_cstring, 0, _INT_STRING_ONSTACK, 0 }; \
  24.     cstring_buffer v; \
  25.     v->str = &v##_cstring_data;


 楼主| zhuomuniao110 发表于 2016-4-30 21:26 | 显示全部楼层
那些年总有个align字段进行内存对齐
  1. /*字节对齐的类型Align,为了优化CPU读取*/
  2. typedef union {
  3.     long        l_dummy;
  4.     double      d_dummy;
  5.     void        *p_dummy;
  6. } Align;

  7. /*标志大小,默认是4字节*/
  8. #define MARK_SIZE       (4)
  9. /*内存块头结点,双向链表结点size,filename,line都是为了调试添加的调试信息.prev和next是双向链表的核心*/
  10. typedef struct {
  11.     int         size;
  12.     char        *filename;
  13.     int         line;
  14.     Header      *prev;
  15.     Header      *next;
  16.     unsigned char       mark[MARK_SIZE];
  17. } HeaderStruct;

  18. /*Align类型的字节大小*/
  19. #define ALIGN_SIZE      (sizeof(Align))
  20. /*这是个不错的技巧,求最小的n使得n*ALIGN_SIZE>=val成立,n,val,ALIGN_SIZE都属于自然数*/
  21. #define revalue_up_align(val)   ((val) ? (((val) - 1) / ALIGN_SIZE + 1) : 0)
  22. /*将HeaderStruct按照Align划分,找到最小的n,使得n*ALIGN_SIZE>=sizeof(HeaderStruct),在自然数集中*/
  23. #define HEADER_ALIGN_SIZE       (revalue_up_align(sizeof(HeaderStruct)))

  24. /*实现了memory.h接口中Header不完全类型,Align是对齐用的,内存结构的头结点.链表链接的主要结点*/
  25. union Header_tag {
  26.     HeaderStruct        s;
  27.     Align               u[HEADER_ALIGN_SIZE];
  28. };
主要看 union Headr_tag 中 Align结构. 保证不同机器上内存是对齐的. 比较古老了. 特别底层的库会见到.


 楼主| zhuomuniao110 发表于 2016-4-30 21:27 | 显示全部楼层
可变参数宏, 那些事
同样直接看下面工程中用的示例
  1. //4.0 控制台打印错误信息, fmt必须是双引号括起来的宏
  2. #ifndef CERR
  3. #define CERR(fmt, ...) \
  4.     fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
  5.          __FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__)
  6. #endif/* !CERR */

  7. //4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量
  8. #ifndef CERR_EXIT
  9. #define CERR_EXIT(fmt,...) \
  10.     CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE)
  11. #endif/* !ERR */

  12. #ifndef IF_CERR
  13. /*
  14. *4.2 if 的 代码检测
  15. *
  16. * 举例:
  17. *        IF_CERR(fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), "socket create error!");
  18. * 遇到问题打印日志直接退出,可以认为是一种简单模板
  19. *    code : 要检测的代码
  20. *  fmt     : 必须是""括起来的字符串宏
  21. *    ...     : 后面的参数,参照printf
  22. */
  23. #define IF_CERR(code, fmt, ...)    \
  24.     if((code) < 0) \
  25.         CERR_EXIT(fmt, ##__VA_ARGS__)
  26. #endif //!IF_CERR

  27. #ifndef IF_CHECK
  28. /*
  29. * 是上面IF_CERR 的简化版很好用
  30. */
  31. #define IF_CHECK(code) \
  32.     if((code) < 0) \
  33.         CERR_EXIT(#code)
  34. #endif // !IF_CHECK
那 传说中的 3颗痣, 就是可变参数宏的一切o(∩_∩)o


 楼主| zhuomuniao110 发表于 2016-4-30 21:28 | 显示全部楼层
简单的谢幕. 还是宏
一个数如何和0比较,真的是 == 吗. 其实好的思路是定义阀值.
  1. //3.0 浮点数据判断宏帮助, __开头表示不希望你使用的宏
  2. #define __DIFF(x, y)                ((x)-(y))                      //两个表达式做差宏
  3. #define __IF_X(x, z)                ((x)<z&&(x)>-z)                //判断宏,z必须是宏常量
  4. #define EQ(x, y, c)                 EQ_ZERO(__DIFF(x,y), c)        //判断x和y是否在误差范围内相等

  5. //3.1 float判断定义的宏
  6. #define _FLOAT_ZERO               (0.000001f)                      //float 0的误差判断值
  7. #define EQ_FLOAT_ZERO(x)          __IF_X(x,_FLOAT_ZERO)            //float 判断x是否为零是返回true
  8. #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 */
chenyongand 发表于 2016-4-30 22:27 | 显示全部楼层
顶起,
371924221 发表于 2016-4-30 22:39 来自手机 | 显示全部楼层
不错,很炫也很实用
whtwhtw 发表于 2016-5-1 10:34 | 显示全部楼层
顶一下
洛理小子 发表于 2016-5-1 13:43 | 显示全部楼层
学习~~~
cnb12345 发表于 2016-5-3 10:26 | 显示全部楼层
quray1985 发表于 2016-5-3 10:39 | 显示全部楼层
为啥入门都用C语言不用汇编语言呢
cowboy2014 发表于 2016-5-4 22:16 | 显示全部楼层
学会C语言再去学习其他的语言就容易多了
 楼主| zhuomuniao110 发表于 2016-5-8 19:49 | 显示全部楼层
cowboy2014 发表于 2016-5-4 22:16
学会C语言再去学习其他的语言就容易多了

是的,C语言是比较早的高级语言,其他语言都借鉴了它的风格。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

233

主题

3529

帖子

11

粉丝
快速回复 在线客服 返回列表 返回顶部