高级程序员装逼指南.docx
《高级程序员装逼指南.docx》由会员分享,可在线阅读,更多相关《高级程序员装逼指南.docx(21页珍藏版)》请在冰豆网上搜索。
高级程序员装逼指南
**前言**
最近网上出了一个《程序员装逼指南》,觉得这个东西其实图样图森破
然后在下跟微博上的一些程序大牛讨论了一下如何装逼,深有感触
程序员嘛,外行人看起来已经是不可理解的奇怪生物了,自然也没必要跟他们再装逼
所以呢,如何对其他程序员装逼就是一门很有学问的事了
于是乎在下手痒写了个《高级程序员装逼指南》,请大家指正
**编程语言**
千万千万千万千万不要说自己是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";
({i1i1:
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