高级程序员装逼指南.docx

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

高级程序员装逼指南.docx

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

高级程序员装逼指南.docx

高级程序员装逼指南

**前言**

最近网上出了一个《程序员装逼指南》,觉得这个东西其实图样图森破

然后在下跟微博上的一些程序大牛讨论了一下如何装逼,深有感触

程序员嘛,外行人看起来已经是不可理解的奇怪生物了,自然也没必要跟他们再装逼

所以呢,如何对其他程序员装逼就是一门很有学问的事了

于是乎在下手痒写了个《高级程序员装逼指南》,请大家指正

 

**编程语言**

千万千万千万千万不要说自己是Java/C#/C++程序员

尽量学一些奇怪的语言,python已经有烂大街的趋势了,写写还是可以,装逼是用不上了

Lisp和Erlang都是装逼的好语言

当然你要是号称会Haskell就更牛逼了,实在不会也没关系,发发跟Haskell有关的状态别人也很装了

没事儿还可以在论坛里喷喷Java/C++有哪些缺点

可是,如果对方先发制人说他出了一种奇怪的语言名字怎么办?

你可以微笑地说,你可知道天下语言皆出自Lisp和Smalltalk?

保准对方愣住3秒钟

 

**操作系统**

首先,妥妥儿的不能用Win,麻瓜才用Win呢

Linux嘛,ubuntu也差了点儿,现在不少人已经用Arch了

懂行儿的人都知道,gentoo和LFS才是真正的装逼利器啊

自己编译神马的,这逼还真不是人人都能装的

再深入的话,你要是用LispMachine工作,就近乎神了

 

**编辑器**

作为一个IDE去死团成员,我承认很大程度上我只是在装逼

纯文本编辑器才是你最终的归宿

Vim是标配,但是只有Emacs才能称得上是神器

“伪装成操作系统的编辑器”并非浪得虚名

当然,想要装逼装得好的话,你还需要学习它的配置语言EmacsLisp

 

**博客**

在CSDN/ITeye/cnblogs这种地方写技术博客确实比在人人上写技术博客好多了

但是你要知道,大牛们都是有自己的个人网站的

而且,一个共同点是,他们的网站都是自己写的html(没有css)并且界面十分难看

整个网站散发着一种“我这的文章都很牛所以界面什么的都不重要”的气质

例如这个:

 

 

**其它**

我习惯称不会写代码的人为“麻瓜”,你也可以有你自己的称呼

不要写Linux/Unix,正确而专业的写法是*nix

手边不要放技术书籍,即使要摆一两本书也要那种自己打印的全英文的手册

或者用铅笔在纸上写代码也是个不错的选择

还可以养成某种奇特的习惯,例如号称自己是Lisper然后各种加(括号)

 

**装逼进阶**

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

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

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

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

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

另,发明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[]="string";

}

 

02[]:

 

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

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

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

chararray[4];

array[0]='a'

1[array]='a';

(2*1)[array]='a';

以上都是合法的。

 

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

   i++;

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

 push       ebx

 push       esi

 push       edi

 lea        edi,[ebp-44h]

 mov        ecx,11h

 mov        eax,0CCCCCCCCh

 repstos   dwordptr[edi]

 intc;

 c=CdeclCall(1,2);

 push       2

 push       1

 call       @ILT+0(_CdeclCall)(00401005)

 add        esp,8

 mov        dwordptr[ebp-4],eax

 c=StdCall(1,2);

 push       2

 push       1

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

 mov        dwordptr[ebp-4],eax

}

 pop        edi

 pop        esi

 pop        ebx

 add        esp,44h

 cmp        ebp,esp

 call       __chkesp(004010f0)

 mov        esp,ebp

 pop        ebp

 ret

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

cdeclCall函数内部:

                  |stdcall函数内部:

                                     |

int__cdeclCdeclCall(inta,intb)  |int__stdcallStdCall(inta,intb)

{                                    |{

  push       ebp                   |  push       ebp

  mov        ebp,esp               |  mov        ebp,esp

  sub        esp,40h               |  sub        esp,40h

  push       ebx                   |  push       ebx

  push       esi                   |  push       esi

  push       edi                   |  push       edi

  lea        edi,[ebp-40h]         |  lea        edi,[ebp-40h]

  mov        ecx,10h               |  mov        ecx,10h

  mov        eax,0CCCCCCCCh        |  mov        eax,0CCCCCCCCh

  repstos   dwordptr[edi]       |  repstos   dwordptr[edi]

 returna+b;                      | returna+b;

  mov        eax,dwordptr[ebp+8] |  mov        eax,dwordptr[ebp+8]

  add        eax,dwordptr[ebp+0Ch]|  add        eax,dwordptr[ebp+0Ch]

}                                    |}

  pop        edi                   |  pop        edi

  pop        esi                   |  pop        esi

  pop        ebx                   |  pop        ebx

  mov        esp,ebp               |  mov        esp,ebp

  pop        ebp                   |  pop        ebp

  ret                               |  ret        8

其中开头指令都是:

pushebp

movebp,esp

subesp,Xxx

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

push       ebx

push       esi

push       edi

最后它们会被释放

pop        edi

pop        esi

pop        ebx

函数返回时会根据调用方式使用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__"string";

({i1

i1:

i2;});

Gcc可以有,VC真没有。

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

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

strcuttest_struct{

   inttest;

};

structtest_structtest=(structtest_struct){1};

Gcc可以有,VC真没有。

宏替换方式两者也不相同,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);

#pragmaoptimize("",on)

voidfoo_only_called(inta,intb)

{

   foo(0,0);

}

voidfoo(inta,intb)

{

   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(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

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

pushebp

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