前言: 第一篇关于技术的博客,还是先写点没用的东西,以前在学校浪费大把的时间,现在追悔莫及,一个居安思危的心态才是好的,只有做技术才让人踏实,充实。
《write solid code》作者steve maguire,在微软开发过MAC版的Excel,两次得到Jolt Productivity Award奖项,牛逼程度毋庸置疑,关于作者的详细信息
1.自己设计使用断言
之前很少用到assert,实现一个自己的断言可以在代码出现问题的时候进入调试程序中,而不是用abort异常处理来中止程序
- //myAssert.h
- #ifdef DEBUG
- void _Assert(char *szMsg, char *szFileName, unsigned long u4LineNum);
- #define ASSERT(condition, szMsg) \
- do { \
- if (condition) \
- NULL; \
- else \
- _Assert(szMsg); \
- } while (0)
- #else
- #define ASSERT(condition, szMsg) NULL
- #endif
- //myAssert.c
- #ifdef DEBUG
- void _Assert(char *szMsg, char *szFileName, unsigned long u4LineNum)
- {
- fflush(stdout);
- fprintf(stderr, "\nAssertion failure in %s, line %u:%s\n",
- szFileName, u4LineNum, szMsg);
- fflush(stderr);
- //abort();
- vDebugRoutine();
- }
- #endif
assert最常见的使用就是检查函数的参数,书中还指出其他的一些使用方法
1)对于未定义的行为,利用assert将其去除,例:malloc对于内存地址重叠的情况没有进行特殊的处理
- /* malloc函数无法处理地址重叠的情况,使用memmove */
- assert(pbTo >= pbFrom + u4Size || pbFrom >= pbTo + u4Size)
2)assert是用来处理非法情况而不是错误情况
- char *strdup(char *szSrc)
- {
- char *szNew;
- assert(szSrc != NULL); //非法情况
- szNew = (char *)malloc(strlen(szSrc) + 1);
- assert(szNew != NULL); //错误情况
- strcpy(szNew, szSrc);
- return szNew;
- }
3)用来检查不可能发生的情况,因为impossible is nothing
- switch (state)
- {
- case 1:
- SomeWork();
- break;
- case 2:
- OtherWork();
- break;
- default: //不可能发生的情况
- assert(0);
- break;
- }
4)利用不同的算法对代码结果进行验证
5)assert是用来调试的代码,release版中是不会出现的,因此调试代码中不应该产生对其他部分的影响
2.为子系统设防
利用一个内存子系统的例子阐述了如何建立子系统并为子系统添加足够的调试信息的方法,对内存的操作是重点应该掌握的
- /* 内存子系统API */
- /* 申请内存 */
- INT32 i4NewMemory(void **pvStart, UINT32 u4Size);
- /* 释放内存 */
- VOID vFreeMemory(void *pvTo);
- /* 重定义内存 */
- INT32 i4ResizeMemory(void **ppvStart, UINT32 u4Size);
- #define cBarbge 0xc3
- INT32 i4NewMemory(void **ppvStart, UINT32 u4Size)
- {
- char **ppc = (char **)ppvStart;
- ASSERT(ppvStart != NULL && u4Size != 0);
- ppc = (char *)malloc(u4Size);
- #ifdef DEBUG
- {
- if (*ppc != NULL)
- memset(*ppc, cGarbage, u4Size);
- }
- #endif
- return (*ppc != NULL);
- }
1)建立子系统
对于内存的申请和释放的操作中加入debug信息,使用比较特别的值来填充,例如0xc3,ARM上的寻址是四字节对齐的,因此奇数的数据以及比较大的数是理想的填充值,申请时填充是为了调试因未初始化就操作数据结构的错误,释放时填充是为了防止指针的悬挂造成对释放内存的错误引用。
2)消除错误随机性
在调试信息中去实现很少发生的情况,这样才可以调试每一个分支,例如在realloc进行扩大操作时,可能会发生内存地址的重新移动,如果因为这个地址处理的不完善引起的错误,具有随机性,因此可以在ResizeMemory 调试的过程中每次都进行新的内存分配。
- INT32 i4ResizeMemory(void **ppvStart, UINT32 u4Size)
- {
- char **ppc = (char **)ppvStart;
- char *pcResize;
- #ifdef DEBUG
- UINT32 u4SizeOld;
- #endif
- ASSERT(ppc != NULL && u4SizeOld != 0);
- #ifdef DEBUG
- {
- u4SizeOld = sizeofBlock(*ppc);
- if (u4sizeNew < u4SizeOld)
- {
- memset((*ppc) + u4SizeNew, cBarbge, u4SizeOld - u4SizeNew);
- }
- else if (u4SizeNew > u4SizeOld)
- {
- char *pcNew;
- if (i4NewMemory(&pcNew, u4SizeNew))
- {
- memcpy(pcNew, *ppc, u4SizeOld);
- vFreeMemory(*ppc);
- *ppc = pcNew;
- }
- }
- }
- #endif
- pcResize = (char *)realloc(*ppc, u4SizeNew);
- ... ...
- }
3)维护内存日志
书中实现的日志系统简洁高效,内存日志主要功能包括两个部分,第一,可以得到内存的size大小以及验证一个指针的有效性;第二,进行内存完整性检查,即保证每一个内存块都有被引用,否则就是发生了内存的泄露
- #ifdef DEBUG
- /* 每个已分配的内存在内存登录中都有一个相应的blockinfo结构 */
- typedef struct rBlockInfo {
- struct rBlockInfo *pbiNext;
- char *pc;
- UINT32 u4Size;
- INT32 i4Ref;
- } rBLOCKINFO;
- INT32 i4CreateBlockInfo(char *pcNew, UINT32 sizeNew);
- VOID vFreeBlockInfo(char *pcToFree);
- VOID vUpdateBlockInfo(char *pcOld, char *pcNew, UINT32 u4SizeNew);
- UINT32 sizeofBlock(char *pc);
- /* 内存完整性检查API */
- VOID vClearMemoryRefs(VOID);
- VOID vNoteMemoryRef(VOID *pc);
- VOID CheckMemoryRefs(VOID);
- INT32 i4ValidPointer(VOID *pc, UINT32 u4Size);
- #endif
其中内存完整性检查首先清除所有blockinfo的内存引用标记,然后遍历代码中的所有内存块并标记,最后查询blockinfo中是否存在未被标记引用的内存。
阅读(2321) | 评论(0) | 转发(1) |