- 论坛徽章:
- 2
|
本帖最后由 OwnWaterloo 于 2010-12-20 22:08 编辑
同6楼。
无论是"用汇编解释C语言", 还是用"仅用C标准解释问题", 都是片面的。
不能将某个编译器的实现当作是C语言,因为"gcc没有警告"这样的理由是站不住脚的。
例如, 从lz的汇编代码可以看出: 在lz使用的机器上, 指针是32位整数。但C语言的指针绝对不是32位整数。
那C语言指针究竟是什么?
其实绝大多数时候, 并不需要了解指针的实际实现方式, 只需要了解指针抽象出的概念就可以编程。
这样既可以避免研究繁琐的汇编代码, 还可以避免无意间写出平台相关代码。
再举一例, 不能因为看到一匹马的颜色, 就说所有马都是这个颜色。
大多数时候, 只需要将马作为交通工具, 颜色并不重要。
回到书中的例子, 书中的说法(我猜测)是为了避免出现这样的代码:
- T* p;
- {
- T v;
- p = &v;
- }
- *p;
复制代码 除开极端情况 —— 无论编译器如何实现栈空间的分配, 无论在block结束后, 该空间是否真的被回 —— 应该认为上述代码是错误的。
因为C语言只保证automatic对象在所属block结束前有效。
最后, 追根究底一下, 是否所有的局部automatic变量都在函数开始处分配?
显然不是。
举例: c99支持VLA:
- void f(void* p);
- void g(int x)
- {
- if (x)
- {
- char buf[x];
- f(buf);
- }
- }
复制代码 gcc -O2 -S -masm=intel -std=c99
_g:
push ebp
mov ebp, esp
push ebx
sub esp, 4
mov eax, DWORD PTR [ebp+8]
test eax, eax
jne L4
mov ebx, DWORD PTR [ebp-4]
leave
ret
.p2align 4,,7
L4:
add eax, 30
mov ebx, esp
and eax, -16
call __alloca
lea eax, [esp+19]
and eax, -16
mov DWORD PTR [esp], eax
call _f
mov esp, ebx
mov ebx, DWORD PTR [ebp-4]
leave
ret
显然, buf是在进入block后分配的。
我记得该书主要关注c89。
那退回c89, 也是存在这样一种情况:
- #ifndef S1
- #define S1 1212
- #endif
- #ifndef S2
- #define S2 326
- #endif
- void f(void* p);
- void g(int x, int y)
- {
- if (x)
- {
- char xx[S1];
- f(xx);
- }
- if (y)
- {
- char yy[S2];
- f(yy);
- }
- }
复制代码 lz可以试试不同的S1, S2组合生成何种代码。
我列出一种情况:
cl /O1 /FAs /c /GS- /DS1=1212 /DS2=1212
得到的汇编代码:
; COMDAT _g
_TEXT SEGMENT
_yy$595 = -1212 ; size = 1212
_xx$593 = -1212 ; size = 1212
...
可以看出, xx和yy的空间是复用的。
也就是说, 在离开xx的block之后, 空间不再属于xx, 随后将用作yy。
是否可以认为xx的空间已被回收挪为它用?
总结, 排除极端情况下, 用汇编去解释C语言是费力不讨好的。
如果能在C语言的抽象层次上完成工作, 将automatic变量的空间认作 (尽管它可能是, 也可能不是) 在block结束后回收, 并不会有什么坏处, 还可以编写编译器无关代码。
即使是高手, 也应该尽可能避免写出编译器相关的代码, 更别说面向初学者的书籍。 |
|