读书人

C软件工程师装逼指南

发布时间: 2012-10-06 17:34:01 作者: rapoo

C程序员装逼指南

C程序员装逼指南

?

文档名称:C程序员装逼指南(C Coder Zhuangbility Manual)文档日期:2010.11.1500 zhuangbility:这可能是我写的最不靠谱的文档了。本文档源于光棍节前的一次玩笑,随后明白,这东西根本没法写。一来,正如回字有几样写法一样迂腐,语言语法级别的东西不是那么上档次;二来,它们确实在实际开发中没有什么用。但语言中确实有一些好的技巧应该被整理收集。比如:char f[] = "char f[] =%c%c%s%c;%cmain() {printf(f,10,34,f,34,10,10);}%c";main() {printf(f,10,34,f,34,10,10);}上面的程序可以输出自己的源代码。这是老牌黑客喜欢玩的quine游戏。下面这个网站收集了很多,我随手抄来:http://www.nyx.net/~gthompso/即使我保留了装逼指南的名字,而实际内容却可能是一些杂项和随想。01 char:严格的说unsigned char、signed char和char是三个类型。char是有无符号由实现决定。在limits.h中记录char的最大值和最小值,一般是有符号的。因此对于参与计算时,将char定义为byte时,最好显式使用unsigned char:typedef unsigned char byte;VC提供了/J编译选项,使char从有符号变成无符号。下面是使用不当char的错误:#define MAKE_DWORD(x) \(DWORD)((x)[0] + ((x)[1] << 8) + ((x)[2] << 16) + ((x)[3] << 24))当x是一个PCHAR,它指向了0x000fccd0。但经过MAKE_DWORD后的结果是0x000ecbd0。因为0xd0和0xcc被当成负数,参与计算时,高位被扩展成1。在进行右移操作时,如果操作数为负,那么右移后最高位还是1。sizeof(char)被定义为1。4byte至少是8位,它需要容下127个ASCII码,并保证它们是非负的。limits.h中定义了当前处理器平台上byte的位数。而char最少需要容下基本字符集(C定义)。在C标准之前的1960年,8bit System/360最终使用ASCII码作为基本编码。1960年之前byte的大小不统一,8, 9, 16, 32, or 36 bits都存在过。1970年之后便统一为8。避免如下写法:sizeof 'a' /* C中值为4,C++中值为1。 */对于局部字符数组,编译器会在全局数据区保存字符"string",在初始化array时,有一个隐式的复制过程,这会带来意想不到的性能损失。void foo(void){    char array[ ] = "string";}02 []:根据C标准,E1[E2]的含义是(*((E1)+(E2)))。因此,只要E1和E2中有一个为指针即可,而没有指定E1或E2:char array[4];array[0] = 'a'1[array] = 'a';(2*1)[array] = 'a';以上都是合法的。03 do-while:do while(0)至少有两种用法:一、代替goto;二、消除宏歧义。下面看例子:do {    p = malloc(0);    if (!p)        break;    .......} while (0);if (p)    p = free();如果代码风格规定确实不能使用goto,那么这里的break可以起到跳转的作用。#define stat_macro(i) do { i = 0; i++; }while(0)if (con)    stat_macro(i);else    i++;do-while(0)巧妙的解决了{}之后的;,并在没有{}的if, else的语句中保持原意。04 fastcall:Borland C++ 5.x fastcall字符、整型、指针类型的参数在传递时依次是EAX、EDX、ECX。而远指针和浮点类型依然是通过堆栈传递的。VC++ 4.x-6.x fastcall字符、整型、指针类型的参数在传递时依次使用ECX、EDX而没有使用第三个寄存器。而__int64、浮点、远指针是通过堆栈传递的。在gcc 3.4.6中引入了fastcall:`fastcall'On the Intel 386, the `fastcall' attribute causes the compiler topass the first argument (if of integral type) in the register ECXand the second argument (if of integral type) in the register EDX.Subsequent and other typed arguments are passed on the stack.The called function will pop the arguments off the stack. If thenumber of arguments is variable all arguments are pushed on thestack.这和VC++的fastcall调用方式是兼容的。而__attribute__((regparm(3)))和bcb中的fastcall兼容。注意:gcc中的fastcall关键字和__a函数_AddressOfReturnAddress和_ReturnAddress。在这里_ReturnAddress()和ret_addr是相等的,都是mov eax,dword ptr [ebp+4]。gcc相关参数是--enable-frame-pointer/-fomit-frame-pointer,__attribute__((noline)), #pragma GCC optimize ("O0")(gcc4.4)或设置函数属性O0禁止了FPO优化。栈的故事到这里还没有结束。众所周知,内存分配往往是性能瓶颈,如果小量并且频繁分配的内存从栈分配而不是从堆分配,那么程序的性能会有一定的提升,并且栈内存不会泄漏,它不需要释放。一般从栈中分配内存是用alloca函数,它会帮你刺探栈内存是否够用,它失败也仅仅因为此。内存分配好后,它会自动更新esp。类似的方案还有C99支持的变长数组,但变长数组不好控制,并且编译器未必兼容。X86的栈回溯的工作原理也是通过EBP寄存器一步一步得到每个栈信息:_asm mov FramePointer, EBP我们知道在函数开始处都有:push ebpmov ebp, esp作为函数的最开始两句代码。这样根据EBP就可以找到所有的函数地址。NextFramePointer = *(PULONG_PTR)(FramePointer);ReturnAddress = *(PULONG_PTR)(FramePointer + sizeof(ULONG_PTR));更多信息请参考作者的另一篇文档《Windows NT Stack Trace》。11 trap:有时程序为了调试等目的,需要主动触发异常。下面两句都可以达到这样的效果:__asm ud2__asm int 3x86为触发错误指令异常定义了0x0f0xb9和0x0f0x0b两个未定义指令,也就是ud1和ud2。int 3是群众喜闻乐见的唤醒调试器指令,值为0xcc,中文为烫。12 bit:用C操作bit是很酷的事:所谓酷就是玩得好很精彩,玩不好就很惨。下面是例子:x & (x-1)                                               将X的最低位1置0x | (x+1)                                               将X的最低位0置1((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b))) << 8))        将a和b合成一个字。((LONG)(((WORD)(a)) | ((DWORD)((WORD)(b))) << 16))      合成双字。((WORD)(((DWORD)(l) >> 16) & 0xFFFF))                   取高位两个字节。((BYTE)(((WORD)(w) >> 8) & 0xFF))                       取高位一个字节。交换高位字节和低位字节:((((x) & 0xff00) >> 8) | (((x) & 0x00ff) << 8))(((x) & 0xff000000) >> 24) |(((x) & 0x00ff0000) >> 8) |(((x) & 0x0000ff00) << 8) |(((x) & 0x000000ff) << 24)SetFlag、ClearFlag、FlagOn标志位操作:x |= flag;x &= flag;x &= ~flag;13 naked:下面的代码中,调用test_naked会触发断言吗?void foo(void){    return;}__declspec(naked)void test_naked(void){    __asm jmp foo    assert(0);}答案是不会。naked函数并不建立函数自己的栈框架,而是用调用者的。test_naked用jmp调用foo,那么调用test_naked的call指令和foo的ret指令会使程序直接返回到调用test_naked的下一个指令。14 inline:You cannot force the compiler to inline a particular function,even with the __forceinline keywordFIXME. GCC extern inline

读书人网 >编程

热点推荐