C语言中本地变量local variable的作用域与生存期

《狂人C》中191页提到“每次运行到auto变量j所在block,会为j寻找存储空间,离开j所在代码模块,j的内存被释放掉。

这是不正确的。

结论应该是:对于C语言而言,本地变量会在栈开始处申请,栈销毁时结束生命。但是本地变量的作用域与所在block相关。之所以编译不通过,是因为这种block之外访问block之内变量的语法是错误的,离开本地变量所在block{},它的作用域无效,但不是说销毁了。

作用域错误是语法层面的。而生存期(存在销毁)与程序栈相关,是运行时的概念,它们是两码事。而且,有没有auto这个keyword修饰,结果都一样,所以auto用不用没有任何意义。

关于C语言程序栈以及调用规范,可以参考我前面翻译的文章:http://sunxiunan.com/?p=1229 也可以查找wiki关于calling conversion方面的资料。

下面我们通过这段代码说明一下:

https://gist.github.com/748095

int main (int argc, char *argv[])

{

 int a = 99;

 int b = a;

 int* pLocala = NULL;

 int* pLocalb = NULL;

 int d = 1;


 printf("hello world \n");

 do{

 int a = 3;

 int b = 23;

 pLocala = &a;

 pLocalb = &b;

 b = a + 5;

 printf("%d %p\n", b, &a);

 }while(0);


 int e = 1;


 b = a;

 printf("%d %p\n", b, &a);

 printf("%p %p %p %p %p %p %p %p\n", &e, pLocalb, pLocala, &d, &pLocalb, &pLocala, &b, &a);

 return(0);

}

如果你运行这段代码(无论是VC++还是GCC),最后printf出来的结果应该是连续的,而&pLocala和&pLocalb其实就是一种while循环中定义的变量a和b所在地址,可以看出来它们夹在变量d与e中间。

我们可以用汇编代码看的更清楚一些,在VC2010生成的汇编代码(C源代码与前面略有不同),完整输出参考 https://gist.github.com/748116

————————————————

;    COMDAT _wmain

_TEXT    SEGMENT

_b$4410 = -32                        ; size = 4

_a$4409 = -20                        ; size = 4

_a$ = -8                        ; size = 4

_argc$ = 8                        ; size = 4

_argv$ = 12                        ; size = 4

; 7    : {

……

; 8    :     int a = 0;

mov    DWORD PTR _a$[ebp], 0

$LN3@wmain:

; 9    :

; 10   :     do

; 11   :     {

; 12   :         auto int a = 3;

mov    DWORD PTR _a$4409[ebp], 3

; 13   :         auto int b = 0;

mov    DWORD PTR _b$4410[ebp], 0

……

——————————————

可以看到中间;12 : auto int a = 3;使用的是这样的汇编代码

mov    DWORD PTR _a$4409[ebp], 3

很显然_a$4409是在程序栈开始位置定义,而不是书中提到的”每次进入block时定义”。

此文发在CU后得到不少有价值的评论

http://bbs.chinaunix.net/thread-1834663-2-1.html

果然如我所料,如果没有O1这个编译选项,VC++2010是不会压缩空间的。
如果加了O1这个”mini space”优化选项,出来的结果也不是OwnWaterloo提到的,临时变量都没有了,优化得很彻底。
我用的是VC2010,过早的优化果然罪恶!(开个玩笑)

无论如何,还是要谢谢OwnWaterloo的说法:勿以汇编释语言。
其实我也同意这个看法,所以尽量以K&R来作为解读文本。
但是对于一些实现相关的内容,如果还有通过自身的解释或者对标准的注解来做说明,那是非常费劲的。
《狂人C》中对++前后缀方式的说明就是如此,尽管键盘农夫试图把++解释的更为通俗易懂深入浅出,但是我看了还是一头雾水。
而当我看过++代码生成的“无优化VC++2010特别为了注释”版本以后,就恍然大悟,源码之下别无秘密。
而且,如果没有我用汇编展示的这个例子,OwnWaterloo大侠也不会出手解释,从这点来讲,也是抛砖引玉。

当然也可以说我这是一种误读,太细化,系统及平台依赖太强,
但是我感觉用汇编来解读部分C语言实现相关的细节,在现阶段对我而言是有帮助的,那就可以了。

int _tmain(int argc, _TCHAR* argv[])
{
int a = 0;
do
{
char buf[1024];
buf[0] = 1;
}while (0);

a = 3;

do
{
char buf[1024];
buf[0] = 93;
}while(0);

return 0;
}

ASM输出结果为:

_TEXT        SEGMENT
_buf$4413 = -2076                                        ; size = 1024
_buf$4409 = -1044                                        ; size = 1024
_a$ = -12                                                ; size = 4
__$ArrayPad$ = -4                                        ; size = 4
_argc$ = 8                                                ; size = 4
_argv$ = 12                                                ; size = 4
_wmain        PROC                                                ; COMDAT

; 7    : {

push        ebp
mov        ebp, esp
sub        esp, 2272                                ; 000008e0H
push        ebx
push        esi
push        edi
lea        edi, DWORD PTR [ebp-2272]
mov        ecx, 568                                ; 00000238H
mov        eax, -858993460                                ; ccccccccH
rep stosd
mov        eax, DWORD PTR ___security_cookie
xor        eax, ebp
mov        DWORD PTR __$ArrayPad$[ebp], eax

; 8    :         int a = 0;

mov        DWORD PTR _a$[ebp], 0
$LN6@wmain:

; 9    :
; 10   :         do
; 11   :         {
; 12   :                 char buf[1024];
; 13   :                 buf[0] = 1;

mov        BYTE PTR _buf$4409[ebp], 1

; 14   :         }while (0);

xor        eax, eax
jne        SHORT $LN6@wmain

; 15   :
; 16   :         a = 3;

mov        DWORD PTR _a$[ebp], 3
$LN3@wmain:

; 17   :
; 18   :         do
; 19   :         {
; 20   :                 char buf[1024];
; 21   :                 buf[0] = 93;

mov        BYTE PTR _buf$4413[ebp], 93                ; 0000005dH

; 22   :         }while(0);

关于Python Web开发我的感悟-兼答wsgi fastcgi uwsgi

有位朋友最近在python.cn接连提问关于python web开发的问题,
当然由于他问题的标题是Python http server,我也很难确定我下面的说法可以回答他。

如果单论http server,通俗点讲,其实就是一个可以相应客户端针对80端口的http请求的一个网络程序。
apache也好,nginx也罢莫不如此。python写一个http server也很简单,只要绑定80端口,解析请求,返回一些格式化好的响应数据就行了。

但是根据列举的这些关键字,其实这位朋友要问的应该是python web开发。
从最简单开始讲,python支持cgi编程,也有标准的cgi模块。
什么是CGI,它不是Python自己独有的东西,而是一个通用的web标准,可以粗陋的认为CGI就是一种用任意语言(那时候都是指perl)写Web程序的标准。最简单的就是用C写一个hello world的程序,只要做一些简单的apache配置,你就可以在网上运行这个程序。

cgi有缺点,就是你自己玩玩还好,想想一个py程序如何运行的?它需要运行py解释器,然后载入这段py脚本,
运行,然后py解释器退出。也就是说每当一个用户访问你的py程序都要经历这个py解释器运行、载入、退出的过程。

设计很不优化。

很显然,这时候需要重用,缓存,对不对?

我印象中fastcgi机制(这是一种机制,早期nginx都是用lighttpd的spawn-fcgi)就是做这样的工作,它会起几个python进程,不退出一直运行着,当你有请求,某一个空闲的python进程就负责载入你的脚本,然后跑,然后继续空闲。

看出区别了吧,python不需要反复启动、退出,这是极为耗费时间的,所以节省了这个过程,就省去了很多时间。
实际上这个机制被普遍运用着,比如office预启动或者很多程序都在系统启动时加载。

当然fastcgi还有其它的机制保证它的运行,我了解不多,就不乱说了。

那么wsgi是什么?
跟wsgi一样概念的有Ruby语言的Rack,可以认为wsgi是一种通用的接口标准或者接口协议,实现了python web程序与服务器之间交互的通用性。就好比JCP一样。有了这个东西,web.py或者bottle或者django等等的python web开发框架,就可以轻松地部署在不同的web server上了,不需要做任何特殊配置(也需要一些小小的配置调整)
http://wsgi.org/wsgi/Frameworks 这里可以看到几乎所有流行的web框架都支持wsgi协议。

uwsgi又是什么?
就我看到的一些东西,uwsgi类似tornadoweb或者flup,也是一种python web server,负责响应python 的web请求,因为apache也好,nginx也罢,它们自己都没有解析动态语言如php的功能,而是分派给其他模块来做,比如apache就可以说内置了php模块,支持的非常爽,让人感觉好像apache就支持php一样。

我说的很简单,其实最重要的是这一句。学Pythonweb开发,应该从动手做开始,而且应该从appengine开始,先省略python部署问题,着重于用python开发web程序,真正跑起来用起来。至于后续的一些概念,应该多学多看,其实我现在对很多python基础也是了解的极为粗浅,但是喜欢没事就查查资料,所以能对这些词语忽悠一些出来。

《狂人C》阅读笔记

将在github的wiki上做持续更新,这里做个copy

https://github.com/saga/sagasw/wiki/%E7%8B%82%E4%BA%BAC

整体问题

对于C标准库的说明都说是编译器实现,这一点是不准确的,参考维基百科http://en.wikipedia.org/wiki /C_standard_library

提到一个真正是莫名其妙不知所云的C语言中国国家标准,这是个什么玩意?谭浩强主编的?

primary expression翻译成初等表达式,这个是败笔,难不成还有中等表达式和高等表达式?我建议如果再版,改做“基本表达式”,元表达式也不妥,一般来讲编程中的“元”多从“meta”这个单词产生,如metaprogramming。

page37, printf(“%d\n”, 1234567890123); // 0x11f71fb04cb 在little-endian机器上,大部分我们机器,如Intel、AMD,结果都应该是1912276171(0x71FB04CB),而在 powerpc的ibook g4 big-endian机器上,结果为287(0x11F)。这个结果是有章可循的。

page38, 应该说明sizeof不会对表达式求值。 sizeof(m++); // m will NOT increase

page62,”后者也是一种错误代码”,感觉提法不容易理解,-> “标准未定义的代码也是应该避免的”。

预处理命令 #definede : typo

注释在编译前被替换成空白字符,这部分待查(gcc,vc)。

page63,风格习惯。命名规范问题,应该用准确的英语做标记。reminder = dividend % divisor; better than ys = bcs % CS;

常见错误,/* comment */,一般常见的IDE会有代码着色功能,如VC++会把注释标记为绿色,而大部分编辑器如vim (textmate)也可以做标记着色。

《狂人C》阅读笔记(1)

Page8,main的原型应该是不需要void作参数的。我试验了codeblock(使用gcc为编译器),没有警告。

另外就是代码应该标记出行号,使用行号指明更清晰一些。

还有一个格式方面的问题是”;“在书中单独出现时都是全角中文”;“这个非常糟糕,很容易误解。

还有一个问题是C99还是C89,我的建议是以C89为主介绍,间杂一些C99,但是C89不应该以”过时“来形容,我倒是觉得C89更主流一些。

page9,关于#include””和#include<>的区别,应该指出后面会讨论的章节更好。

page14,关于中文做标识符、变量名、函数名的解释,反而让人比较迷糊。其实我觉得作为一本针对初学者的书籍,最好不要在开始碰这个方面,可以在后面加入一些说明补充。

page16,如表1-1所示,应该说”有些keyword”是C99新增关键字。另外我觉得这个C99其实意义不大,说了反而让人迷惑。在后面附录补充说明一下就可以了。

page19,风格习惯一节,如果说的更详细一点就好了,太简单。另外《狂人C》的代码风格是({}三种风格中)最不常见的一种,应该稍微提一下三种不同风格。

page20,开始一个段落介绍如何让printf分成print\换行加f这种,完全没必要,让人更混乱,而且代码示例如果从缩进来看还是错的。

page20,我觉得应该是”任何一个C程序必须有而且只能有一个入口函数,这个函数绝大多数情况下定义为main。“VC++和GCC都可以通过编译选项修改入口函数(entry function),如果用VC++,通常名字为_tmain()。还有就是main函数有两个可不可以?如果抬杠的讲,通过预定义编译选项,可以定义两个main,嘿嘿。

page20,printf()函数是由C标准库提供(C standard library),而C标准库大多数由操作系统编写者提供,通常情况下GCC只负责把printf()与标准库代码实现编译链接到一起。关于标准库,也可以选择比如DietC这种第三方。windows sdk也提供了标准库的实现,我不是很清楚,但是理论上说GCC应该是可以使用windows SDK提供的include和library的。

page25,应该介绍一下D(ecimal),H(ex)这些单词的完整形式,更好一些。

page29,对于学习、考试、面试而言,我觉得”同名”是一个很值得考的知识点,当然实际工作中就不要这么写了。

page31,由于字体的原因,/* */和//看上去非常奇怪,尤其是//就好像中间有个空格一样。

page31,关于注释,我不太同意书中说法。个人觉得注释只应该在该使用的时候使用,如何注释何时注释,代码大全和程序设计实践这两本书介绍的非常全。

page32,关于变量的定义,K&R英文版(以下提到K&R都指的是英文版页码)的page195说的非常清楚,《狂人C》的提法只说了其中一方面,也就是storage object这一面。不完整。

page32,变量使用之前必须声明(declaration),这一点《狂人C》的说法是错误的,对于C语言来讲,声明 (declaration)与定义(definition)是很有区别的两回事,不可以互换使用。K&R page40说的非常清楚。而定义(definition)在K&R的page250有说明。