高级程序员装逼指南Word文件下载.docx

上传人:b****8 文档编号:22698770 上传时间:2023-02-05 格式:DOCX 页数:21 大小:24.40KB
下载 相关 举报
高级程序员装逼指南Word文件下载.docx_第1页
第1页 / 共21页
高级程序员装逼指南Word文件下载.docx_第2页
第2页 / 共21页
高级程序员装逼指南Word文件下载.docx_第3页
第3页 / 共21页
高级程序员装逼指南Word文件下载.docx_第4页
第4页 / 共21页
高级程序员装逼指南Word文件下载.docx_第5页
第5页 / 共21页
点击查看更多>>
下载资源
资源描述

高级程序员装逼指南Word文件下载.docx

《高级程序员装逼指南Word文件下载.docx》由会员分享,可在线阅读,更多相关《高级程序员装逼指南Word文件下载.docx(21页珍藏版)》请在冰豆网上搜索。

高级程序员装逼指南Word文件下载.docx

**装逼进阶**

老是装逼也不成,所以我一直在寻找一种秒杀一切程序员的装逼之法

说实话,程序员的世界里有三种人

大部分是不会写代码的麻瓜,然后是程序员这个群体本身

他们认为唯一比程序员牛逼的,就是搞数学的人了

所以嘛,你要真想装个牛逼,就去学好傅立叶变换吧

另,发明Lisp和Python的人都是数学家,高得纳大神也是数学教授

还有更多装逼之法,想到再加

C程序员装逼指南

文档名称:

C程序员装逼指南(CCoderZhuangbilityManual)

文档日期:

00zhuangbility:

这可能是我写的最不靠谱的文档了。

本文档源于光棍节前的一次玩笑,随后明白,

这东西根本没法写。

一来,正如回字有几样写法一样迂腐,语言语法级别的东西

不是那么上档次;

二来,它们确实在实际开发中没有什么用。

但语言中确实有一些

好的技巧应该被整理收集。

比如:

charf[]="

charf[]=%c%c%s%c;

%cmain(){printf(f,10,34,f,34,10,10);

}%c"

;

main(){printf(f,10,34,f,34,10,10);

}

上面的程序可以输出自己的源代码。

这是老牌黑客喜欢玩的quine游戏。

下面这个

网站收集了很多,我随手抄来:

即使我保留了装逼指南的名字,而实际内容却可能是一些杂项和随想。

01char:

严格的说unsignedchar、signedchar和char是三个类型。

char是有无符号由实现

决定。

在中记录char的最大值和最小值,一般是有符号的。

因此对于参与

计算时,将char定义为byte时,最好显式使用unsignedchar:

typedefunsignedcharbyte;

VC提供了/J编译选项,使char从有符号变成无符号。

下面是使用不当char的错误:

#defineMAKE_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码,并保证它们

是非负的。

中定义了当前处理器平台上byte的位数。

而char最少需要容下

基本字符集(C定义)。

在C标准之前的1960年,8bitSystem/360最终使用ASCII码作为

基本编码。

1960年之前byte的大小不统一,8,9,16,32,or36bits都存在过。

1970年之后便统一为8。

避免如下写法:

sizeof'

a'

/*C中值为4,C++中值为1。

*/

对于局部字符数组,编译器会在全局数据区保存字符"

string"

,在初始化array时,

有一个隐式的复制过程,这会带来意想不到的性能损失。

voidfoo(void)

{

chararray[]="

02[]:

根据C标准,E1[E2]的含义是(*((E1)+(E2)))。

因此,只要E1和E2中有一个为指针

即可,而没有指定E1或E2:

chararray[4];

array[0]='

1[array]='

(2*1)[array]='

以上都是合法的。

03do-while:

dowhile(0)至少有两种用法:

一、代替goto;

二、消除宏歧义。

下面看例子:

do{

p=malloc(0);

if(!

p)

break;

.......

}while(0);

if(p)

p=free();

如果代码风格规定确实不能使用goto,那么这里的break可以起到跳转的作用。

#definestat_macro(i)do{i=0;

i++;

}while(0)

if(con)

stat_macro(i);

else

do-while(0)巧妙的解决了{}之后的;

,并在没有{}的if,else的语句中保持原意。

04fastcall:

BorlandC++fastcall

字符、整型、指针类型的参数在传递时依次是EAX、EDX、ECX。

而远指针和浮点类型依然是通过堆栈传递的。

VC++fastcall

字符、整型、指针类型的参数在传递时依次使用ECX、EDX而没有使用第三个寄存器。

而__int64、浮点、远指针是通过堆栈传递的。

在gcc3.4.6中引入了fastcall:

`fastcall'

OntheIntel386,the`fastcall'

attributecausesthecompilerto

passthefirstargument(ifofintegraltype)intheregisterECX

andthesecondargument(ifofintegraltype)intheregisterEDX.

Subsequentandothertypedargumentsarepassedonthestack.

Thecalledfunctionwillpoptheargumentsoffthestack.Ifthe

numberofargumentsisvariableallargumentsarepushedonthe

stack.

这和VC++的fastcall调用方式是兼容的。

而__attribute__((regparm(3)))和bcb中

的fastcall兼容。

注意:

gcc中的fastcall关键字和__attribute__((regparm(3)))

是不相同的。

05function:

参数个数可变的函数必须标记为__cdecl。

显式标记为__cdecl、__fastcall或

__stdcall的函数使用指定的调用约定。

采用的参数个数可变的函数总是使用

__cdecl调用约定。

对于C,__cdecl命名约定使用前面带下划线(_)的

函数名;

除非声明为extern"

C"

,否则C++函数将使用不同的名称修饰方案。

__fastcall函数的一些参数传入寄存器(对于x86处理器,为ECX和EDX),

而其余的参数按从右向左的顺序入栈。

被调用函数在返回之前从堆栈中弹出参数。

esp指向栈顶,栈顶地址值却是比较小的值。

每次push的结果都使esp减小,

而pop则相反。

cdecl调用按声明顺序由右向左压栈,调用函数清栈。

函数名前加"

_"

stdcall调用按声明顺序由右向左压栈,被调用函数清栈。

,后加"

@"

和参数个数。

下面是在main中调用两种函数的汇编例子:

main()

push 

ebp

mov 

ebp,esp

sub 

esp,44h

ebx

esi

edi

lea 

edi,[ebp-44h]

ecx,11h

eax,0CCCCCCCCh

repstos 

dwordptr[edi]

intc;

c=CdeclCall(1,2);

2

1

call 

@ILT+0(_CdeclCall)(00401005)

add 

esp,8

dwordptr[ebp-4],eax

c=StdCall(1,2);

@ILT+5(_StdCall@8)(0040100a)

pop 

cmp 

__chkesp(004010f0)

esp,ebp

ret

CdeclCall比StdCall多了清栈指令addesp,8。

cdeclCall函数内部:

|stdcall函数内部:

|

int__cdeclCdeclCall(inta,intb) 

|int__stdcallStdCall(inta,intb)

|{

ebp 

ebp,esp 

esp,40h 

esp,40h

ebx 

esi 

edi 

edi,[ebp-40h] 

edi,[ebp-40h]

ecx,10h 

ecx,10h

eax,0CCCCCCCCh 

dwordptr[edi] 

returna+b;

eax,dwordptr[ebp+8] 

eax,dwordptr[ebp+8]

eax,dwordptr[ebp+0Ch]| 

eax,dwordptr[ebp+0Ch]

|}

esp,ebp 

ret 

8

其中开头指令都是:

pushebp

movebp,esp

subesp,Xxx

其后的三个保存寄存器的指令是规定:

push 

最后它们会被释放

pop 

函数返回时会根据调用方式使用retn或者ret。

在给函数下断点时esp以上是该函数参数,以下是该函数局部变量。

06wchar_t:

wchar_t在C++中是内置的关键字,但在C中不是。

C是通过typedef定义的。

VC的/Zc:

wchar_t和gcc的-fshort-wchar选项都可以控制wchar_t的宽度。

在Windows平台上sizeof(wchar_t)为2,而Linux平台上为4。

-fshort-wchar

选项可以使wchar_t变成unsignedshortint。

07macro:

预处理可以计算uintmax_t大小的常量。

C标准定义:

typedeflonglongintmax_t;

typedefunsignedlonglonguintmax_t;

利用预处理器这个特点可以处理更大的数值计算:

#definetest_macro(1<

63)

#iftest_macro==0

#error"

error!

"

#endif

unsignedlonglongtest=test_macro;

在C程序中,常量的大小被限制在unsignedlong之内,对于longlong大小常量可以

通过预处理器计算。

上面的error!

并没有输出,因为test_macro没有溢出。

08__VA_ARGS__:

变参宏使得你可以定义类似的宏:

#defineLOG(format,...)printf(format,__VA_ARGS__)

LOG("

%s%d"

str,count);

__VA_ARGS__是系统预定义宏,被自动替换为参数列表。

这个特性在GCC和VC中都是支持的。

然而Gcc还一种独有的语法扩展:

#defineLOG(fmt...)printf(fmt)

即使没有标准的支持,Gcc依然可以支持变参数宏。

09gcc:

void*类型在gcc可以当作整数计算,VC不可以。

这导致sizeof(void)值为1,而VC

中,它的值为零,并打印一条警告。

__func__为C99定义。

Gcc支持__func__和__FUNCTION__,然而VC只支持不标准

的__FUNCTION__。

__func__是字符串数组,而__FUNCTION__是宏,可以连接字符:

__FUNCTIN__"

({i1<

i2?

i1:

i2;

});

Gcc可以有,VC真没有。

这是Gcc扩展语法,{}内的表达式值可以作为整个({})的值。

这就可以使宏的行为和函数更相近,并同样起到和do-while(0)相同的消除歧义作用。

strcuttest_struct{

inttest;

};

structtest_structtest=(structtest_struct){1};

宏替换方式两者也不相同,Gcc嵌套替换宏,VC会一次全部替换宏。

10stack:

通过栈中返回地址和变量的压栈关系,我们可以得到调用者的地址:

voidfoo(inta,intb)

printf("

%xn"

((unsignedlong*)&

a)[-1]);

下面这个技巧可能是最危险的,它需要四个条件才能正常工作。

1.源代码函数的编写顺序和生成顺序相对应,并且先实现的函数在低地址,

后实现的在高地址。

2.禁止增量链接。

增量链接会生成@ITL跳转表,函数首地址只是表中偏移地址。

增量编译的原理主要是通过改写跳转表代替重新编译函数调用。

3.禁止内联。

只有禁止内联才能生成函数调用框架(/Oy)。

4.禁止FPO。

打开FPO的程序有参数压栈指令而没有call、ret指令。

#include<

>

#pragmacomment(linker,"

/INCREMENTAL:

NO"

#pragmaoptimize("

y"

off)

__declspec(noinline)voidfoo(inta,intb);

__declspec(noinline)voidfoo_only_called(inta,intb);

on)

voidfoo_only_called(inta,intb)

foo(0,0);

unsignedlongret_addr=((unsignedlong*)&

a)[-1];

assert((ret_addr<

(unsignedlong)foo)&

&

(ret_addr>

(unsignedlong)foo_only_called));

intmain(intargc,char*argv[])

foo_only_called(0,0);

foo只能从foo_only_called中调用,main调用foo会引发断言。

如果函数没有参数,则需要用内置函数_AddressOfReturnAddress和_ReturnAddress。

在这里_ReturnAddress()和ret_addr是相等的,都是moveax,dwordptr[ebp+4]。

gcc相关参数是--enable-frame-pointer/-fomit-frame-pointer,

__attribute__((noline)),#pragmaGCCoptimize("

O0"

)或设置函数属性

O0禁止了FPO优化。

栈的故事到这里还没有结束。

众所周知,内存分配往往是性能瓶颈,如果小量并且

频繁分配的内存从栈分配而不是从堆分配,那么程序的性能会有一定的提升,并且

栈内存不会泄漏,它不需要释放。

一般从栈中分配内存是用alloca函数,它会帮你

刺探栈内存是否够用,它失败也仅仅因为此。

内存分配好后,它会自动更新esp。

类似的方案还有C99支持的变长数组,但变长数组不好控制,并且编译器未必兼容。

X86的栈回溯的工作原理也是通过EBP寄存器一步一步得到每个栈信息:

_asmmovFramePointer,EBP

我们知道在函数开始处都有:

movebp,esp

作为函数的最开始两句代码。

这样根据EBP就可以找到所有的函数地址。

NextFramePointer=*(PULONG_PTR)(FramePointer);

ReturnAddress=*(PULONG_PTR)(FramePointer+sizeof(ULONG_PTR));

更多信息请参考作者的另一篇文档《WindowsNTStackTrace》。

11trap:

有时程序为了调试等目的,需要主动触发异常。

下面两句都可以达到这样的效果:

__asmud2

__asmint3

x86为触发错误指令异常定义了0x0f0xb9和0x0f0x0b两个未概念指令,也就是

ud1和ud2。

int3是群众喜闻乐见的唤醒调试器指令,值为0xcc,中文为烫。

12bit:

用C操作bit是很酷的事:

所谓酷就是玩得好很精彩,玩不好就很惨。

下面是例子:

x&

(x-1) 

将X的最低位1置0

x|(x+1) 

将X的最低位0置1

((WORD)(((B

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 其它课程

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1