17th March 2007

Blog 搬家

由于GFW的原因,wordpress网站始终被block。只有将家搬到了这里。现在就充分体现了wordpress的好处了: 原有的数据直接export/import就搞定了。这就是我用wordpress的主要原因。好了,以后自己会多多灌溉这里。

posted in 涂鸦 | 0 Comments

17th March 2007

牛角尖(三) — 变长数组 (VLA, Variable Length Array)

本文作者:campos 本文出处:http://www.mykernelspace.com  (转载请保留此行,谢谢)

    最近跟SH同学讨论了一个源于他们公司开发中的一个bug的问题。 先来一段代码

#include <iostream>
using namespace std;
int main()
{
int size=10;
int array[size];
array[1] = 13;
cout<<array[1]<<endl;
}

是不是觉得很奇怪,打小就知道c/c++数组必须是定长的,这里居然出现了用变量作为size的数组 ,而且输出的结果确实是13。想知道背后发生了什么,看看汇编就知道了(g++带上 -S参数)

pushl %ebp ; 保留原EBP值
movl %esp, %ebp ;把栈顶位置放入EBP
subl $8, %esp ; reserve 8个bytes on stack
andl $-16, %esp ; reserve 16个bytes on stack
movl %esp, %edx
movl $10, -4(%ebp) ;在栈顶放入local变量
movl -4(%ebp), %eax ; local变量的值放入EAX
sall $2, %eax ; 这里是关键,把变量乘以4得到实际数组的大小
subl %eax, %esp ;把ESP向下移size*4 个字节, 得到数组的连续空间
movl %esp, %eax ;EAX指向数组起始位置
movl $13, 4(%eax) ;将13存入数组第2个位置
movl %edx, %esp ;恢复ESP
movl $0, %eax ;设置程序返回值,默认为0
leave
ret

从上面的汇编程序可以看出,实际上程序确实把size的值作为参数,在local stack上申请了一片连续的空间。可能这个还不够surprising,因为C++标准里面允许用const int作为数组的大小。这里的size的值也是在compile-time就知道了,不足为奇。 那就来看看下面这段代码:

int main()
{
int size;
int array[size];
array[1] = 13;
return 3;
}

这个时候size的值是未知的,我们来看看g++是如何处理的(这里只列出关键代码了):

movl -4(%ebp), %eax
sall $2, %eax
subl %eax, %esp
movl %esp, %eax
movl $13, 4(%eax)

No surprise,程序还是从栈顶保存size变量的地方取得数组的大小,然后在local stack上分配空间,最后在第二个元素位置放入我们需要的值。所以c++确实已经支持了这种用法。但是这也是很危险的,比如说SH同学提到的bug,以及很简单的上面这个例子。编译的时候即使用上了 -Wall g++也不会冒一个泡。但是运行的时候确实是 segmentation fault。

总的来说,在编译的时候VLA和FLA(Fixed Length Array)非常不同。 对于FLA,无论是在程序的什么部分定义,空间都会在程序最开始分配;这点和local variable一样。 VLA就非常像malloc出来的内存,因为它的空间是执行到那个指令的时候才会分配,如果size是一个local variable,就从栈顶那里找,如果是像strlen这样的函数返回值,就会在函数执行了得到返回值之后分配内存。但是一个重大的区别就是VLA的内存都是在stack上的,所以SH同学碰到了stack overflow的问题(因为程序编译的时候并不检查或者没有办法检查VLA大小)。

VLA是已经是C99和C++98标准的一部分了。但是用的时候确实需要小心。

posted in 编程珠玑 | 1 Comment

11th March 2007

STL 源码研读笔记(1)– auto_ptr

本文作者:campos 本文出处:http://www.mykernelspace.com  (转载请保留此行,谢谢)

      很久没上来写东西了,最近认真学习了一下C++,发现真是博大精深,以前可以说是白痴一个。当读到Template的时候,确实很好奇,因为自己确实想彻底弄清楚模板这块。看完后就想找个地方试试看自己看懂了没有,很自然的就想到了STL。
STL确实是一个很实用的东西,最重要的是他写得通用而且已经作为了C++的标准放入了所有的C++ distribution中。想看看自己模板学好了没有,就去读STL。以前也想过研究这个library,后来发现自己被一大堆的”<”和”>”彻底打懵了。现在再钻进去看,觉得清楚多了。候先生的《STL源码剖析》确实是好书,可惜电子版只有前面四章,托国内同学买也未果,所以就有了这个“STL 研读笔记系列” -- 自己把感兴趣的部分读懂,然后做做笔记。

一开始当然要从最简单的template class开始。什么最简单?vector? list? 我觉得是auto_ptr。 这个auto_ptr是一个非常简单的smart pointer类。说它简单是因为它只具有自动释放内存(析构)的功能,没有对象指针引用计数的功能。好,废话少说,这就来看看这个类。

//一个wrapper class,这个类模拟了auto_ptr的reference类型。可以被一个传回auto_ptr值的函数赋值。
template<typename _Tp1> struct auto_ptr_ref
{
_Tp1* _M_ptr;
explicit auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }
};

//下面这个是真正的auto_ptr模板类。它唯一的成员数据就是一个模板类型的指针,这个是真正指向需要“保护”的对象的指针
template<typename _Tp> class auto_ptr
{
private:
_Tp* _M_ptr;
public: typedef _Tp element_type;

//构造函数,explicit表示这是一个禁止constructor conversion的构造函数,传入的参数必须是_Tp*类型
explicit auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }

//同类型拷贝构造函数,参数是另外一个auto_ptr,但是这个auto_ptr要释放自己所包含的指针。正式这个move而不是copy的语义导致了“千万不要使用类型为auto_ptr的容器”!
//这个模板类比较特殊,数据是指针,所以允许不同类型之间的拷贝
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }

//不同类型的拷贝构造函数,同样也是move的办法
template<typename _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) {}

//同类型的auto_ptr赋值操作符,__a释放自己包含的指针,本模板类用reset更新自己的指针为__a的指针
auto_ptr& operator=(auto_ptr& __a) throw()
{
reset(__a.release());
return *this;
}

//不同类型的auto_ptr赋值操作符
template<typename _Tp1> auto_ptr& operator=(auto_ptr<_Tp1>& __a) throw()
{
reset(__a.release());
return *this;
}

//析构函数,当auto_ptr走出scope的时候,自己释放对象。但是这里的局限就是delete,而不支持释放数组对象数组的delete []。所以决定了auto_ptr只能hold单一指针。
~auto_ptr() {delete _M_ptr;}


//重载dereference操作符。
element_type& operator* const throw()
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return *_M_ptr;
}

//重载“member access from a pointer” 操作符。注意这里的返回值和上面的dereference返回值不同,这里返回的是成员指针。当需要调用auto_ptr->func()的时候,实际上是调用了 auto_ptr->()->func(),这个是C++内部处理的。
element_type* operator->() const throw()
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr !=0 );
return _M_ptr;
}


//获取成员指针函数
element_type* get() const throw() {return _M_ptr; }

//获取成员指针函数,同时将成员指针清零
element_type* release() throw()
{
element_type* __tmp = _M_ptr;
_M_ptr = 0;
return __tmp;
}

//释放成员指针所指向的对象,同时给成员指针赋新值
void reset(element_type* __p = 0) throw()
{
if (__p != _M_ptr)
{
delete _M_ptr;
_M_ptr = __p;
}
}


//从auto_ptr_ref构造auto_ptr的构造函数
auto_ptr(auto_ptr_ref<element_type> __ref) throw() : _M_ptr(__ref._M_ptr) { }

//从auto_ptr_ref的赋值操作符,包含self-assignment 检查。前面的赋值操作符没有这个检查,因为当时只是指针的赋值,不涉及内存的释放。
auto_ptr& operator=(auto_ptr_ref<element_type> __ref) throw()
{
if (__ref._M_ptr != this->get())
{
delete _M_ptr;
_M_ptr = __ref._M_ptr;
}
return *this;
}

//本auto_ptr到其他任意类型auto_ptr_ref的conversion操作符重载
template<typename _Tp1>
operator auto_ptr_ref<_Tp1>() throw()
{ return auto_ptr_ref<_Tp1>(this->release()); }

//本auto_ptr到其他任意类型的auto_ptr的conversion操作符重载
template<typename _Tp1>
operator auto_ptr<_Tp1>() throw()
{ return auto_ptr<_Tp1>(this->release()); }
};

好了,写到这里自己也温习了一遍,也收获不小,下次再选一个稍微复杂一点的来分析一下。

posted in 编程珠玑 | 1 Comment