nginx源码初读(1)--让烦恼从数据结构开始(ngx_cdecl/ngx_int/ngx_log)
2016-02-18 15:12
711 查看
抛去所有的模块和各种定义的数据结构,对于一个没看过这么大工程的小白来说,太乱!乱的不要不要,光是各种数据结构的含义,就让我要抓狂了。好吧,我并不是说它结构不好,相反我感觉代码写的太棒了。。就是一时间。。接受不了。废话不说了,让烦恼开始吧,唉╮(╯▽╰)╭
第一点:ngx_cdecl
是的,就是这个ngx_cdecl,之前知道在源码里面有__cdecl和__stdcall等东西,在这儿的ngx_cdecl又是什么,按理说是一样的,但是查到定义后是这样的:
对,就是一个空的define,那它有啥用?当然有用,而且用得很好,要不怎么说人家是好代码呢,后路留的多好,避免以后填坑。nginx中使用这个宏是为了跨平台支持,方便调整函数调用方式。在遇到问题时,可以修改上面的定义为:
要不怎么说多看代码有好处呢,这就是思想啊。好了再解释下cdecl和stdcall:
__cdecl:C Declaration的缩写,表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。
调用函数的代码和被调函数必须采用相同的函数的调用约定,程序才能正常运行。
__cdecl和__stdcall的区别:__cdecl是调用者清理参数占用的堆栈,__stdcall是被调函数清理参数占用的堆栈。假设函数A是__stdcall,函数B调用函数A。你必须通过函数声明告诉编译器,函数A是__stdcall。编译器自然会产生正确的调用代码。如果函数A是__stdcall,但在引用函数A的地方,你却告诉编译器,函数A是__cdecl方式,编译器产生__cdecl方式的代码,与函数A的调用约定不一致,就会发生错误。
注意事项:由于__stdcall的被调函数在编译时就必须知道传入参数的准确数目(被调函数要清理堆栈),所以不能支持变参数函数,例如printf。而且如果调用者使用了不正确的参数数目,会导致堆栈错误。
第二点:ngx_int_t & ngx_uint_t
在stdint.h中找到intptr的定义:
定义中的注释说明,这两个类型是定义用来当作指针使用的,因为指针的长度和长整型一直是一样的,由于指针就是一个数组索引的存在,系统内核在操作内存时,就是将内存当做一个大数组,而指针就是数组索引/下标,内核程序员使用这种特殊的整型来接受内存地址值、操作内存相比使用指针更加直观,不容易犯错。
intptr_t 这个类型可以被安全的在 void * 和 整数间转换,对于写跨 64 位平台的程序非常重要。也就是说,当你需要把指针作为一个整数来运算时,转换成 intptr_t 才是安全的,可以在运算完毕安全的转回指针类型,也避免了对指针解引用产生的bug。
由代码中的宏可以看出,intptr_t的长度是适应不同平台的,当编译环境是64位时,intptr_t是long int,否则就是int。
那么nginx中使用它来typedef出ngx_int_t是想要干吗?为啥不用int来?
[b]从类型名来看很容易理解为普通的int型,我想nginx使用它是因为intptr在定义的时候就自适应平台,根据平台来变化自己的长度,作者就不用自己再定义一次了。
第三点:ngx_log_t
为啥要先struct又在另一个头文件里typedef,因为现在用得是c,必须struct XXX来使用,所以typedef封装一下,让用户不知道它的具体结构。
第一点:ngx_cdecl
int ngx_cdecl main(int argc, char *const *argv);
是的,就是这个ngx_cdecl,之前知道在源码里面有__cdecl和__stdcall等东西,在这儿的ngx_cdecl又是什么,按理说是一样的,但是查到定义后是这样的:
#define ngx_cdecl
对,就是一个空的define,那它有啥用?当然有用,而且用得很好,要不怎么说人家是好代码呢,后路留的多好,避免以后填坑。nginx中使用这个宏是为了跨平台支持,方便调整函数调用方式。在遇到问题时,可以修改上面的定义为:
#define ngx_cdecl stdcal
要不怎么说多看代码有好处呢,这就是思想啊。好了再解释下cdecl和stdcall:
__cdecl:C Declaration的缩写,表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。
调用函数的代码和被调函数必须采用相同的函数的调用约定,程序才能正常运行。
__cdecl和__stdcall的区别:__cdecl是调用者清理参数占用的堆栈,__stdcall是被调函数清理参数占用的堆栈。假设函数A是__stdcall,函数B调用函数A。你必须通过函数声明告诉编译器,函数A是__stdcall。编译器自然会产生正确的调用代码。如果函数A是__stdcall,但在引用函数A的地方,你却告诉编译器,函数A是__cdecl方式,编译器产生__cdecl方式的代码,与函数A的调用约定不一致,就会发生错误。
注意事项:由于__stdcall的被调函数在编译时就必须知道传入参数的准确数目(被调函数要清理堆栈),所以不能支持变参数函数,例如printf。而且如果调用者使用了不正确的参数数目,会导致堆栈错误。
第二点:ngx_int_t & ngx_uint_t
typedef intptr_t ngx_int_t; typedef uintptr_t ngx_uint_t;
在stdint.h中找到intptr的定义:
117 /* Types for `void *' pointers. */ 118 #if __WORDSIZE == 64 119 # ifndef __intptr_t_defined 120 typedef long int intptr_t; 121 # define __intptr_t_defined 122 # endif 123 typedef unsigned long int uintptr_t; 124 #else 125 # ifndef __intptr_t_defined 126 typedef int intptr_t; 127 # define __intptr_t_defined 128 # endif 129 typedef unsigned int uintptr_t; 130 #endif
定义中的注释说明,这两个类型是定义用来当作指针使用的,因为指针的长度和长整型一直是一样的,由于指针就是一个数组索引的存在,系统内核在操作内存时,就是将内存当做一个大数组,而指针就是数组索引/下标,内核程序员使用这种特殊的整型来接受内存地址值、操作内存相比使用指针更加直观,不容易犯错。
intptr_t 这个类型可以被安全的在 void * 和 整数间转换,对于写跨 64 位平台的程序非常重要。也就是说,当你需要把指针作为一个整数来运算时,转换成 intptr_t 才是安全的,可以在运算完毕安全的转回指针类型,也避免了对指针解引用产生的bug。
由代码中的宏可以看出,intptr_t的长度是适应不同平台的,当编译环境是64位时,intptr_t是long int,否则就是int。
那么nginx中使用它来typedef出ngx_int_t是想要干吗?为啥不用int来?
[b]从类型名来看很容易理解为普通的int型,我想nginx使用它是因为intptr在定义的时候就自适应平台,根据平台来变化自己的长度,作者就不用自己再定义一次了。
第三点:ngx_log_t
20 typedef struct ngx_log_s ngx_log_t; 45 typedef u_char *(*ngx_log_handler_pt) (ngx_log_t *log, u_char *buf, size_t len); 46 typedef void (*ngx_log_writer_pt) (ngx_log_t *log, ngx_uint_t level, 47 u_char *buf, size_t len); // 看log.c中程序len的计算都是通过指针相减 48 49 50 struct ngx_log_s { 51 ngx_uint_t log_level; /* 日志级别,有define:0~8的值和对应的string数组 * cycle->log->log_level = NGX_LOG_INFO; * if (log->log_level < level && !debug_connection) break; * log->log_level |= d; */ 52 ngx_open_file_t *file; /* 日志文件,结构内有文件标志fd和文件名name * n = ngx_write_fd(log->file->fd, errstr, p - errstr); * if (log->file->fd == ngx_stderr) { wrote_stderr = 1;} * log->file = ngx_conf_open_file(cycle, &error_log); * if (new_log->file == NULL) { return NGX_CONF_ERROR;} */ 53 54 ngx_atomic_uint_t connection; /* 原子单元链接,定义为整形变量(与平台有关) * if (log->connection) { p = ngx_slprintf(p, last, "*%uA ", log->connection); } */ 55 56 time_t disk_full_time; /* 写入full filesystem时间,代码中经常与ngx_time()比较 * if (ngx_time() == log->disk_full_time) { /* * on FreeBSD writing to a full filesystem with enabled softupdates * may block process for much longer time than writing to non-full * filesystem, so we skip writing to a log for one second */ goto next; } * if (n == -1 && ngx_errno == NGX_ENOSPC) { log->disk_full_time = ngx_time(); } */ 57 58 ngx_log_handler_pt handler; /* 日志处理器,见45行定义 * if (level != NGX_LOG_DEBUG && log->handler) { p = log->handler(log, p, last - p); } * cln->handler = ngx_log_memory_cleanup; */ 59 void *data; /* 日志数据 * cln->data = new_log; */ 60 61 ngx_log_writer_pt writer; /* 日志写入函数,见46行定义 * if (log->writer) { log->writer(log, level, errstr, p - errstr); goto next; } */ 62 void *wdata; /* 日志内容 /* 日志写入函数,见46行定义 * if (log->writer) { log->writer(log, level, errstr, p - errstr); goto next; } */ 63 64 /* 65 * we declare "action" as "char *" because the actions are usually 66 * the static strings and in the "u_char *" case we have to override 67 * their types all the time 68 */ 69 70 char *action; /* 操作 * c->log->action = "waiting for request"; * c->log->action = "reading client request headers"; * c->log->action = "keepalive"; * log->action = "closing request"; * p = ngx_snprintf(buf, len, " while %s", log->action); */ 71 72 ngx_log_t *next; 73 };
为啥要先struct又在另一个头文件里typedef,因为现在用得是c,必须struct XXX来使用,所以typedef封装一下,让用户不知道它的具体结构。
相关文章推荐
- 数据结构与算法
- 图的邻接矩阵c语言表示(无向网)---《数据结构》算法7.2
- 数据结构 魔王语言C++
- 数据结构——树
- 数据结构之栈(二)
- 数据结构之栈(一)
- 数据结构在程序中的实现及表现形式
- 数据结构绪论
- 数据结构:二叉树
- Java数据结构----栈(Stack)源码分析和个人简单实现
- 学习笔记------数据结构(C语言版)数组的顺序存储
- Python笔记(2)——数据类型和数据结构
- 数据结构学习笔记——二叉树的类型定义
- Nginx源码分析 - 基础数据结构篇 - 字符串结构 ngx_string.c
- 舞蹈链--求精密覆盖(数据结构)
- Dex文件结构及对应的数据结构
- 数据结构图文解析之:二分查找及与其相关的几个问题解析
- 数据结构实验图论一:基于邻接矩阵的广度优先搜索遍历
- 数据结构
- 通过一个实例学会时间复杂度的计算