高级程序员装逼指南

** 前言 **

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

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

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

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

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

** 编程语言 **

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

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

Lisp和Erlang都是装逼的好语言

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

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

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

你可以微笑地说,你可知道天下语言皆出自Lisp和Smalltalk?保准对方愣住3秒钟

** 操作系统 **

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

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

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

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

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

** 编辑器 **

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

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

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

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

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

** 博客 **

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

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

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

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

例如这个:http://xahlee.org/index.html

** 其它 **

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

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

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

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

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

** 装逼进阶 **

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

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

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

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

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

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

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

C程序员装逼指南

文档名称:C程序员装逼指南(C Coder Zhuangbility Manual)
文档日期:2010.11.15

00 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 to
pass the first argument (if of integral type) in the register ECX
and 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 the
number of arguments is variable all arguments are pushed on the
stack.
这和VC++的fastcall调用方式是兼容的。而__attribute__((regparm(3)))和bcb中
的fastcall兼容。注意:gcc中的fastcall关键字和__attribute__((regparm(3)))
是不相同的。

05 function:

参数个数可变的函数必须标记为 __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
rep stos    dword ptr [edi]
int c;
c = CdeclCall(1, 2);
push        2
push        1
call        @ILT+0(_CdeclCall) (00401005)
add         esp,8
mov         dword ptr [ebp-4],eax
c = StdCall(1, 2);
push        2
push        1
call        @ILT+5(_StdCall@8) (0040100a)
mov         dword ptr [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多了清栈指令add esp,8。

cdeclCall函数内部:                   |stdcall函数内部:
|
int __cdecl CdeclCall(int a, int b)   |int __stdcall StdCall(int a, int b)
{                                     |{
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
rep stos    dword ptr [edi]        |   rep stos    dword ptr [edi]
return a + b;                       |  return a + b;
mov         eax,dword ptr [ebp+8]  |   mov         eax,dword ptr [ebp+8]
add         eax,dword ptr [ebp+0Ch]|   add         eax,dword ptr [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

其中开头指令都是:
push ebp
mov ebp,esp
sub esp,Xxx

其后的三个保存寄存器的指令是规定:
push        ebx
push        esi
push        edi

最后它们会被释放
pop         edi
pop         esi
pop         ebx

函数返回时会根据调用方式使用ret n 或者 ret。
在给函数下断点时esp以上是该函数参数,以下是该函数局部变量。

06 wchar_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变成unsigned short int。

07 macro:

预处理可以计算uintmax_t大小的常量。C标准定义:

typedef long long intmax_t;
typedef unsigned long long uintmax_t;

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

#define test_macro (1<<63)
#if test_macro == 0
#error “error!”
#endif

unsigned long long test = test_macro;

在C程序中,常量的大小被限制在unsigned long之内,对于long long大小常量可以
通过预处理器计算。上面的error!并没有输出,因为test_macro没有溢出。

08 __VA_ARGS__:

变参宏使得你可以定义类似的宏:
#define LOG( format, … ) printf( format, __VA_ARGS__ )
LOG( “%s %d”, str, count );
__VA_ARGS__是系统预定义宏,被自动替换为参数列表。
这个特性在GCC和VC中都是支持的。然而Gcc还一种独有的语法扩展:

#define LOG(fmt…) printf(fmt)

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

09 gcc:

void * 类型在gcc可以当作整数计算,VC不可以。这导致sizeof(void)值为1,而VC
中,它的值为零,并打印一条警告。

__func__为C99定义。Gcc支持__func__和__FUNCTION__,然而VC只支持不标准
的__FUNCTION__。__func__是字符串数组,而__FUNCTION__是宏,可以连接字符:

__FUNCTIN__”string”;

({i1 < i2 ? i1 : i2;});

Gcc可以有,VC真没有。这是Gcc扩展语法,{}内的表达式值可以作为整个({})的值。
这就可以使宏的行为和函数更相近,并同样起到和do-while(0)相同的消除歧义作用。

strcut test_struct {
int test;
};

struct test_struct test = (struct test_struct){1};

Gcc可以有,VC真没有。

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

10 stack:

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

void foo(int a, int b)
{
printf(“%xn”, ((unsigned long *)&a)[-1]);
}

下面这个技巧可能是最危险的,它需要四个条件才能正常工作。
1.源代码函数的编写顺序和生成顺序相对应,并且先实现的函数在低地址,
后实现的在高地址。
2.禁止增量链接。增量链接会生成@ITL跳转表,函数首地址只是表中偏移地址。
增量编译的原理主要是通过改写跳转表代替重新编译函数调用。
3.禁止内联。只有禁止内联才能生成函数调用框架(/Oy)。
4.禁止FPO。打开FPO的程序有参数压栈指令而没有call、ret指令。

#include <assert.h>
#pragma comment(linker, “/INCREMENTAL:NO”)
#pragma optimize(“y”, off)
__declspec(noinline) void foo(int a, int b);
__declspec(noinline) void foo_only_called(int a, int b);
#pragma optimize(“”, on)
void foo_only_called(int a, int b)
{
foo(0, 0);
}
void foo(int a, int b)
{
unsigned long ret_addr = ((unsigned long *)&a)[-1];
assert((ret_addr < (unsigned long)foo) &&
(ret_addr > (unsigned long)foo_only_called));
}
int main(int argc, char* argv[])
{
foo_only_called(0, 0);
foo(0, 0);
}

foo只能从foo_only_called中调用,main调用foo会引发断言。
如果函数没有参数,则需要用内置函数_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 ebp
mov ebp, esp

作为函数的最开始两句代码。这样根据EBP就可以找到所有的函数地址。
NextFramePointer = *(PULONG_PTR)(FramePointer);
ReturnAddress = *(PULONG_PTR)(FramePointer + sizeof(ULONG_PTR));
更多信息请参考作者的另一篇文档《Windows NT Stack Trace》。

11 trap:

有时程序为了调试等目的,需要主动触发异常。下面两句都可以达到这样的效果:

__asm ud2
__asm int 3

x86为触发错误指令异常定义了0x0f0xb9和0x0f0x0b两个未定义指令,也就是
ud1和ud2。int 3是群众喜闻乐见的唤醒调试器指令,值为0xcc,中文为烫。

12 bit:

用C操作bit是很酷的事:所谓酷就是玩得好很精彩,玩不好就很惨。下面是例子:

x & (x-1)                                               将X的最低位1置0
x | (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 keyword
FIXME. GCC extern inline

○你们这桌交错敬酒,你敬我一杯,我敬你一杯,这叫全相连.
○你们这桌有人提议咱们男士敬女士们一杯,这叫组相连.
○BOSS自己提了一瓶过来说兄弟们再把这个解决了这叫alpha测试.
○BOSS让服务生端了二瓶过来,隔着桌子喊,你们这桌再把这个解决了,这叫beta测试.
○经理过来要敬大家,你们都说不行了,经理指着酒量最大的人说×××你代表大家把这杯干了,这叫白盒测试.
○经理过来要敬大家,你们都说不行了,经理说我不管,出个人把这杯干了,这叫黑盒测试.
○又有人醉醺醺地过来要敬大家,一看不认识不是你们公司的这叫地址错误.
○你拍拍这人肩膀说兄弟你是对面厅的吧这叫重定向.
○你一人自斟自饮喝闷酒这叫PC
○你们这桌相互敬酒这叫LAN
○你们和别桌流动敬酒这叫WAN
○你们一哥们醉了上二楼别的公司的庆祝宴上去敬酒这叫INTERNET
○你一人到领导那桌说张总我敬你一个王总我敬你一个……这叫PPP
○你们一群人到领导那桌说张总我们敬你一个王总我们敬你一个……这叫ADSL
○BOSS举起一杯说我代表公司向你们项目组的成功表示祝贺这叫B2B
○项目经理举起一杯说我代表项目组向张工的付出表示感谢这叫B2C
○你也举杯说张哥我对你的景仰如指令流水滔滔不绝这叫C2C
○你一哥们不行了喝点水也吐这叫插入异常
○你也觉得不行了上洗手间想解决一下可怎么也吐不出来这叫删除异常
○你们这桌子萧是头儿他一喝大家跟着喝这叫 Primary Key
○你们这桌坐了俩头儿,别桌的都过来敬酒这叫Foreign Key
○叶子菊说不行我不喝酒的,经理说不行今天都得喝点这叫 NOT NULL
○经理从别桌转回来了问小李喝了吗喝了多少,这叫 Check
○Boss说在座的各位今儿个都得尽兴这叫Assertion全局约束
○乔大饼是海量偏不多喝这叫资源闲置
○阿甘不行了还要喝这叫超频
○小牛过来跟你碰杯这叫过程
○小吴过来跟你碰杯完了还要谈感受这叫函数
○你们这桌喝没了,这叫缺页中断
○让小姐拿两瓶过来这叫请求分页
○让她把酒放下把空瓶收拾收拾这叫页面置换
○一群人来给你敬酒你说等一下慢慢来这叫P操作
○你喝得豪气冲天放下杯说下一个我喝谁的这叫V操作
○你正在喝着有人来跟你碰杯你说等一下这叫阻塞
○那人看你放下杯给你满上这叫就绪
○稍歇了一下那人说该咱俩喝了这叫运行
○你刚喝了半口手机响了这叫中断
○你说不好意思哥们等一下这叫挂起
○你接完电话挂机了那人说咱俩继续这叫激活
○你来这杯终于喝完了那人回他们桌子上去了这叫终止
○又一哥们过来说你喝我就喝这叫同步
○一激动他把自个儿杯子打了这成了互斥
○你把这杯推给他说你先喝他又推回来说你先喝这叫死锁
○你说那咱们都不喝了这叫撤销
○贝贝喝了1.44ml就醉了退出来了这叫软盘
○达兰木坐那儿不动光猛喝这叫硬盘
○曹无名光看不喝这叫光盘
○anetople上这桌喝又上那桌喝这叫U盘
○大家你一杯我一杯地喝这叫聊天室
○一个人猛喝其他人跟着喝这叫论坛
○你站起来猛灌别人都看你喝这叫博客
○曹无名暗恋何有名拉她上沙发那边喝这叫QQ
○要敬你的人太多了他们一人一杯都放在你面前这叫SPOOLING
○你实在不行了钻桌子底下去了这叫作业中止
○来不及上洗手间就吐了这叫ERROR
○脸色煞白起不来了这叫FATE ERROR
○经理紧张了说你没事吧这叫询问
○你说不出话了这叫丢失应答
○一哥们开始拨120了这叫紧急修复
○Boss也急了说你们先撤大家也都散了吧这叫解除
○120来了这叫GAME OVER
○120先走了这叫关机

○大家都出来各自上车了这叫切断电源

from:http://www.starming.com/index.php?action=plugin&v=wave&tpl=union&ac=viewgrouppost&gid=32767&tid=1000003430

发表评论

电子邮件地址不会被公开。

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>