高级程序员装逼指南.docx
- 文档编号:23874657
- 上传时间:2023-05-21
- 格式:DOCX
- 页数:19
- 大小:25.59KB
高级程序员装逼指南.docx
《高级程序员装逼指南.docx》由会员分享,可在线阅读,更多相关《高级程序员装逼指南.docx(19页珍藏版)》请在冰豆网上搜索。
高级程序员装逼指南
**前言**
最近网上出了一个《程序员装逼指南》,觉得这个东西其实图样图森破
然后在下跟微博上的一些程序大牛讨论了一下如何装逼,深有感触
程序员嘛,外行人看起来已经是不可理解的奇怪生物了,自然也没必要跟他们再装逼
所以呢,如何对其他程序员装逼就是一门很有学问的事了
于是乎在下手痒写了个《高级程序员装逼指南》,请大家指正
**编程语言**
千万千万千万千万不要说自己是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)并且界面十分难看
整个网站散发着一种“我这的文章都很牛所以界面什么的都不重要”的气质
例如这个:
http:
//xahlee.org/index.html
**其它**
我习惯称不会写代码的人为“麻瓜”,你也可以有你自己的称呼
不要写Linux/Unix,正确而专业的写法是*nix
手边不要放技术书籍,即使要摆一两本书也要那种自己打印的全英文的手册
或者用铅笔在纸上写代码也是个不错的选择
还可以养成某种奇特的习惯,例如号称自己是Lisper然后各种加(括号)
**装逼进阶**
老是装逼也不成,所以我一直在寻找一种秒杀一切程序员的装逼之法
说实话,程序员的世界里有三种人
大部分是不会写代码的麻瓜,然后是程序员这个群体本身
他们认为唯一比程序员牛逼的,就是搞数学的人了
所以嘛,你要真想装个牛逼,就去学好傅立叶变换吧
另,发明Lisp和Python的人都是数学家,高得纳大神也是数学教授
还有更多装逼之法,想到再加
C程序员装逼指南
文档名称:
C程序员装逼指南(CCoderZhuangbilityManual)
文档日期:
2010.11.15
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是有无符号由实现
决定。
在limits.h中记录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码,并保证它们
是非负的。
limits.h中定义了当前处理器平台上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++5.xfastcall
字符、整型、指针类型的参数在传递时依次是EAX、EDX、ECX。
而远指针和浮点类型依然是通过堆栈传递的。
VC++4.x-6.xfastcall
字符、整型、指针类型的参数在传递时依次使用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")(gcc4.4)或设置函数属性 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:
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 高级程序员 指南