Programming

C++ string literals

从一个例子谈起,下面的例子摘自《C++ Templates》。
[cpp]

include

include

template
void ref (T const& x)
{
std::cout << "x in ref(T const&): "
<< typeid(x).name() << 'n';
}

template
void nonref (T x)
{
std::cout << "x in nonref(T): "
<< typeid(x).name() << 'n';
}

int main()
{
ref("hello");
nonref("hello");
}
[/cpp]

用g++编译,得到运行结果如下:

x in ref(T const&): A6_c
x in nonref(T): PKc

(注:A6_c表示:array(A) of 6 chars (c);PKc表示:pointer (P) to const (K) char (c)。)

初看到这个例子我有些惊讶,为什么同样是字符串常量,通过引用传递和非引用传递差别就这么大呢?其实真想想也没什么大惊小怪的。看下面的分析。

首先,对于字符串常量,在C++标准里的类型已经发生了变化,以前是char [],现在是const char[],参考C++06 2.13.4:

An ordinary string literal has type “array of n const char” and static storage duration,
where n is the size of the string as defined below, and is initialized with the given characters.

但为了保持兼容,一部分当char []来使用的代码仍然有效。这不像C99,C99照顾兼容照顾得更厉害,所以这点仍然没变,它只说了一句:

If the program attempts to modify such an array, the behavior is undefined.

有些跑题了……咳,所以,当引用传递时自然是原来的类型const char[]。

而直接传递呢?直接传递其实也是const char[]!我没开玩笑!想想啊,它的类型怎么能会突然就变呢?不可能!问题是为什么typeid看到的是const char *?很简单啊,因为这是函数!当数组作为函数参数传递时会退化为指针,我们耳熟能详的这句话在C++里也适用啊!所以,这里拧把的不是模板,而是引用,别被模板给吓怕了!

如果你仔细看看原书那一节,你会发现还有个问题比较拧把:不同长度的同类型数组其实也不是一个类型,这也不难理解,放C里看看你就知道了:C里的一切类型皆是连续的内存块,数组更是,不同长度的同类型数组长度还是不一样啊,怎么能是同一个类型呢?所以这就有问题了,不同长度的字符串常量还不能作为同一个类型直接传递进模板了?如果我们用引用传递的话,是,可如果不用的话就行了,这正是解决方法!或者更干脆,直接用std::string。

virtual function 的例子

本来这没什么可说的,C++中的虚函数不难理解,可是我看到不少教程中的例子给得不行,多说一句得。

那些教程给出的是类似下面的例子:

Base *p = new Derived;
p->virt_func();

我认为这个例子不行,只能显示虚函数啥样子,却不能说明白为什么要有虚函数。其实,至少我认为,虚函数是为了给函数传递参数用的,我使用基类指针作为函数参数类型的定义,但调用时却可以传递给它子类的指针,可虚函数的行为却是实参类型中的对应方法。我认为这才是虚函数的意义,为此我写了下面一个程序,顺便练练了刚学成的template recursion。

[cpp]

include

template
class C: public C
{
public:
virtual void foo();
};

template
void C::foo()
{
std::cout<<id<<std::endl;
}

template
class C
{
public:
virtual void foo();
};

void C::foo()
{
std::cout<<0<<std::endl;
}

void func(C *arg)
{
arg->foo();
}

int main()
{
C C1;
C C2;
C C3;

func(&amp;C1);
func(&amp;C2);
func(&amp;C3);
return 0;

}
[/cpp]

Returning void

在一个 void 函数中返回void是不是允许的?这是一个比较有意思的问题。为此我查了一下C99和C++标准,它们的规定是不同的。

在标准C中,这是不允许的,参见C99 6.8.6.4:

A return statement with an expression shall not appear in a function whose return type
is void. A return statement without an expression shall only appear in a function
whose return type is void.
写得很清楚,没什么好说的。C++不同,C++标准是完全允许的,第6.6.3节第三段写道:
A return statement with an expression of type “cv void”can be used only in functions
with a return type of cv void; the expression is evaluated just before the function
returns to its caller.
我想C++之所以这么做是为了模板考虑的,看下面的代码片段:
[cpp]
template
T func(T (*pf)())
{
return pf();
}

void foo() {}

int main()
{
func(foo);
return 0;
}
[/cpp]

auto_ptr 和 shared_ptr

偶然的机会看到boost里还有一个shared_ptr型智能指针,费解,C++标准库里不是有auto_ptr了已经?我们先翻出源代码来看看。

先看auto_ptr,因为它比较简单。既然它在标准库里,那么我们就在gcc的代码中找,恩,最后确定在libstdc++-v3/include/backward/auto_ptr.h里面。说实话,auto_ptr真是太简单了,里面的私有变量就一个:

private:
_Tp* _M_ptr;

其它操作皆是围绕这个进行的,你自己猜都能猜到,看看吧,构造函数中的一个:
[cpp]
explicit
auto_ptr(element_type p = 0) throw() : _M_ptr(p) { }
[/cpp]
析构函数:
[cpp]
~auto_ptr() { delete _M_ptr; }
[/cpp]
重载的赋值运算符:
[cpp]
template
auto_ptr&
operator=(auto_ptr& a) throw()
{
reset(
a.release());
return
this;
}
[/cpp]
调用了自己的reset和release方法,按住不表,下面会介绍。重载的提领运算符:
[cpp]
element_type&
operator() const throw()
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return
_M_ptr;
}
[/cpp]

成员运算符:
[cpp]
element_type*
operator->() const throw()
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return _M_ptr;
}
[/cpp]

get()方法:

[cpp]
element_type*
get() const throw() { return _M_ptr; }
[/cpp]

release()方法:
[cpp]
element_type
release() throw()
{
element_type
tmp = _M_ptr;
_M_ptr = 0;
return
tmp;
}
[/cpp]

reset()方法:
[cpp]
void
reset(element_type* p = 0) throw()
{
if (
p != _M_ptr)
{
delete _M_ptr;
_M_ptr = __p;
}
}
[/cpp]

这些应该没什么难理解的,可能除了那个成员运算符,只说一句:C++会把foo->bar()翻译成:(foo.operator->())->bar()。

从上面我们可以看出,auto_ptr明显没有计数引用,而shared_ptr和它的最大区别就是它有计数。shared_ptr的源代码在boost/shared_ptr.hpp里。先看它的私有变量:

T * px; // contained pointer
boost::detail::shared_count pn; // reference counter

多了一个引用计数器,是一个boost::detail::shared_count类型,再翻源代码boost/detail/shared_count.hpp,只看用到的三个方法:
[cpp]
bool unique() const // nothrow
{
return use_count() == 1;
}

void swap(shared_count &amp; r) // nothrow
{
    sp_counted_base * tmp = r.pi_;
    r.pi_ = pi_;
    pi_ = tmp;
}

long use_count() const // nothrow
{
    return pi_ != 0? pi_-&gt;use_count(): 0;
}

[/cpp]
呃,还有一个不能漏了,赋值运算符:
[cpp]
sharedcount & operator= (shared_count const & r) // nothrow
{
sp_counted_base * tmp = r.pi
;

    if( tmp != pi_ )
    {
        if( tmp != 0 ) tmp-&gt;add_ref_copy();
        if( pi_ != 0 ) pi_-&gt;release();
        pi_ = tmp;
    }

    return *this;
}

[/cpp]
再回过头去看shared_ptr的源代码:
[cpp]
reference operator () const // never throws
{
BOOST_ASSERT(px != 0);
return
px;
}

T * operator-&gt; () const // never throws
{
    BOOST_ASSERT(px != 0);
    return px;
}

T * get() const // never throws
{
    return px;
}

bool operator! () const // never throws
{
    return px == 0;
}

bool unique() const // never throws
{
    return pn.unique();
}

long use_count() const // never throws
{
    return pn.use_count();
}

void swap(shared_ptr &amp; other) // never throws
{
    std::swap(px, other.px);
    pn.swap(other.pn);
}

shared_ptr &amp; operator=( shared_ptr &amp;&amp; r ) // never throws
{
    this_type( static_cast( r ) ).swap( *this );
    return *this;
}

[/cpp]

这样一切都明了了。所以,auto_ptr和shared_ptr在使用上的区别也好理解了,下面的小程序可以展示:

[cpp]

include

include

include

using namespace boost;
using std::cout;
using std::endl;
using std::auto_ptr;

class A
{
public:
void print()
{
cout<<"hello"<<endl;
}
};

int main()
{
auto_ptr aptr1(new A);
auto_ptr
aptr2;

aptr2 = aptr1;
aptr2-&gt;print(); //Ok
cout&lt;&lt;&quot;pointer in aptr1 is: &quot;&lt;&lt;aptr1.get()&lt;print(); //Wrong!

A * a = new A;
shared_ptr<a> sptr1(a);
shared_ptr<a> sptr2(sptr1); //alright
sptr2 = sptr1;
sptr2-&gt;print(); //ok
sptr1-&gt;print(); //ok

int * b = new int;
shared_ptr sptr3(b);
shared_ptr sptr4(b); //WRONG!!

return 0;

}
[/cpp]

编程太难,我们去写脚本吧!

perl.com 上发布了Larry Wall的一篇文章,题目就是本文的标题,非常有意思。

文中从脚本语言的历史谈起,提到了脚本语言的一些特征,和设计脚本语言的方方面面。当然了,Larry写这篇文章的目的之一肯定就是鼓吹Perl6了,看来他是力挺Perl6的。总之,推荐读一下这篇文章。

另,很期待perl6的到来,不知道得等到哪一年的圣诞节啦~~!

对标准的一条评论

The standard is written in a variant of English sometimes called
standardese. It’s designed (or at least intended) to be
absolutely precise and unambiguous, even at the cost of
understandability. It doesn’t always succeed with its intent,
but in all cases, precision and a lack of ambiguity have
precedence over readability.
非常精辟的评价~!来自James Kanze。

reentrant,thread-safe 和 async-signal-safe

先上定义吧,POSIX对它们的定义分别是:

Reentrant Function

A function whose effect, when called by two or more threads, is guaranteed to be as if the threads each executed the function one after another in an undefined order, even if the actual execution is interleaved.

Thread-Safe

A function that may be safely invoked concurrently by multiple threads. Each function defined in the System Interfaces volume of IEEE Std 1003.1-2001 is thread-safe unless explicitly stated otherwise.

Async-Signal-Safe Function

A function that may be invoked, without restriction, from signal-catching functions. No function is async-signal-safe unless explicitly described as such.
可重入我们都清楚,顾名思义,就是可以重新进入,进一步讲就是,用相同的输入,每次调用函数一定会返回相同的结果。这就是可重入。wikipedia上)有更严谨的定义:

  • Must hold no static (global) non-constant data.
  • Must not return the address to static (global) non-constant data.
  • Must work only on the data provided to it by the caller.
  • Must not rely on locks to singleton resources.
  • Must not call non-reentrant computer programs or routines.
    然后是线程安全,从定义上看,它仅要求了可以安全地被线程并发执行。这是一个相对较低的要求,因为它内部可以访问全局变量或静态变量,不过需要加锁,也就是说,只要是在线程可控之中的,每次调用它返回不同的结果也没关系。到这里我们可以看出:可重入函数一定是线程安全的,而反之未必。wikipedia上也写道:
    Therefore, reentrancy is a more fundamental property than thread-safety and by definition, leads to thread-safety: Every reentrant function is thread-safe, however, not every thread-safe function is reentrant.
    例子,有很多,最出名的莫过于strtok(3),我们认识可重入这个概念就是从它开始的,它内部适用了静态变量,显然是不可重入的(它的可重入版是strtok_r(3))。其次应该是malloc(3),嘿嘿,其实也很明显,我就不多说了。但是,strtok(3)不是线程安全的,而malloc(3)是。

还有一个概念我们不常碰到,那就是异步信号安全,它其实也很简单,就是一个函数可以在信号处理函数中被安全地调用。看起来它似乎和线程安全类似,其实不然,我们知道信号是异步产生的,但是,信号处理函数是打断主函数(相对于信号处理函数)后执行,执行完后又返回主函数中去的。也就是说,它不是并发的!

一个函数,它访问了全局变量,那么它就是不可重入的,不过我们可以把它变成线程安全的,加上锁就可以,但是这种方法并不会把它变成异步信号安全的,而几乎可以肯定的是,使用了锁的一定不是信号安全的(除非屏蔽了信号,显然),信号完全可以在锁加上后解开前到来,然后就很可能形成死锁…… 这里有个很好的例子。所以,可重入的函数也一定是异步信号安全的,而反之未必。可以参考IBM上一篇不错的文章

关于异步信号安全的函数列表可以参考man 7 signal;关于线程安全的函数列表可以参考APUE第12.5节 ;关于可重入函数列表,可参考APUE第10.6节。另请参阅

用 sigaction(2) 的原因

Linux man page中提到:

The behavior of signal() varies across Unix versions, and has also varied
historically across different versions of Linux. Avoid its use: use sigaction(2) instead.

The only portable use of signal() is to set a signal’s disposition to SIG_DFL or SIG_IGN. The semantics when using signal() to establish a signal handler vary across systems (and POSIX.1 explicitly permits this variation); do not use it for this purpose.
所以signal(2)的可移植性首先就差了一截子。还有就是,signal(2)也不能像sigaction(2)那样去屏蔽信号,POSIX中提到:
When a signal is caught by a signal-catching function installed by
sigaction(), a new signal mask is calculated and installed for the
duration of the signal-catching function (or until a call to either
sigprocmask() or sigsuspend() is made). This mask is formed by taking
the union of the current signal mask and the value of the sa_mask for
the signal being delivered unless SA_NODEFER or
SA_RESETHAND is set, And then including the signal being
delivered. If and when the user’s signal handler returns normally, the
original signal mask is restored.

IDE, ATA, SCSI 等等

关于硬盘的术语有一大堆,而且和很多别的计算机术语一样,界定不清,含义模糊。在这里简单澄清一下,但我不排除你看了之后会更糊涂。:^)

IDE:Integrated Drive Electronics 的缩写。最早的硬盘接口,后来被 ATA 标准所取代,准确地讲应该是 ATA-1。所以有时也被称为 IDE/ATA,可参见其历史介绍。后来,几乎和ATA-1同时出现的 EIDE(Enhanced IDE )又被规范化为 ATA-2。

ATA:Advanced Technology Attachment 的缩写,存储设备(并非仅限硬盘)的接口标准。有时也被称为 IDE。IDE 在 ATA 标准出现之前就有了,所以估计这个也是因为历史原因。不过根据wikipedia记载,这两个术语还是有些许不同之处的:

The terms “integrated drive electronics” (IDE), “enhanced IDE” and “EIDE” have come to be used interchangeably with ATA (now Parallel ATA). However the terms “IDE” and “EIDE” are at best imprecise. Every ATA drive is an “integrated drive electronics” drive, but not every “integrated drive electronics” drive is an ATA drive, as the term also correctly describes SCSI drives: They have the drive controllers on board and present the drive to the host as an array of blocks.
后来就都是 ATA 了。又因为 SATA(Serial ATA,即 ATA-7)的出现,所以老的 ATA 又都被称为 PATA(Parallel ATA)了。

SCSI:Small Computer System Interface 的缩写,另一个完全不同的标准,定义了内部的命令,协议和接口。SCSI更快,当然也更贵,多用在中,高性能服务器上,不过apple的mac也是最早使用它的计算机之一。新的SCSI标准,Serial Attached SCSI又被简称为SAS

SCSI 和前面的总起来并列为三个标准:PATA,SATA,SCSI。其中SATA应该是会继续流行的PC机硬盘接口。

HDD:Hard Disk Drive 的缩写,就是我们通常所说的硬盘,磁盘。

SSD:Solid State Drive 的缩写,通常我们称为固态硬盘。SSD耗电少,抗震性能好,但比起HDD来要贵一些,而且容量通常也不像HDD那么大。

RAID:Redundant Array of Independent Disks 的缩写,就是我们通常所说的磁盘阵列。RAID是对多块硬盘的组合,需要硬件支持(貌似也有从文件系统层支持RAID的,btrfs?)。根据不同的组合方式,RAID又分为RAID0到RAID7,详细参考这里

参考:

http://www.91wp.com/Article/jswz/200704/350.html
http://www.pcbuyerbeware.co.uk/IDE.htm
http://www.pcguide.com/ref/hdd/if/ide/over.htm
http://en.wikipedia.org/wiki/RAID
http://en.wikipedia.org/wiki/SCSI
http://en.wikipedia.org/wiki/AT_Attachment

获取 CPU 序列号

受lshw的启发,自己动手写了一个获取Intel CPU序列号的小程序,顺便练练gcc内嵌汇编。过程中参考了Intel手册和lshw源代码。:-)

注意:并不是所有的Intel CPU都有序列号。我的CPU就不行,又在别的机器上测的。

[c]

include

typedef struct {
unsigned int eax;
unsigned int edx;
unsigned int ecx;
} cpusn_t;

int getcpusn(cpusnt *sn)
{
sn->eax = 1;
asm (
“cpuidnt”
:”=c”(sn->ecx), “=d”(sn->edx), “=a” (sn->eax)
:”2” (sn->eax)
);
if ((sn->edx & (1<eax = 3;
__asm
(
“cpuidnt”
:”=c”(sn->ecx), “=d”(sn->edx), “=a” (sn->eax)
:”2” (sn->eax)
);
return 0;
}
void print_cpusn(cpusn_t sn)
{
char buf[30];
snprintf(buf, sizeof(buf), “%04X-%04X-%04X-%04X-%04X-%04X”,
sn.eax >> 16, sn.eax & 0xffff,
sn.edx >> 16, sn.edx & 0xffff,
sn.ecx >> 16, sn.ecx & 0xffff);
printf(“%sn”, buf);

}

int main(void)
{
cpusn_t sn = {0, 0, 0};
if (get_cpusn(&sn) == 0) {
print_cpusn(sn);
} else {
//printf(“edx = %xn”, sn.edx);
printf(“Doesn’t support this.n”);
}
return 0;
}
[/c]