第2条 字符串格式化的"动物庄园"之一(2)
本例中,42恰好足够小,以至于5B大小的结果" 42\0"恰巧能够放在smallBuf中。然而,设想某一天代码改成了这样:
- char smallBuf[5];
- int value = 12108642;
- PrettyFormat(value, buf); // 哦!
- assert(value == 12108642); // 这个断言很可能会失败!
这会导致smallBuf尾部之后的区域也被改写,而倘若编译器恰巧让value(在内存中)紧跟在smallBuf之后的话,被改写的区域就是value值本身占用的空间了!
我们无法轻易地改善示例2-1的安全性。的确,我们可以让PrettyFormat()接受缓冲区的长度并对sprintf()的返回值进行检查,但这等于是事后诸葛亮。具体做法如下:
- // 糟糕的主意:丝毫没有改观
- //
- void PrettyFormat(int i, char* buf, int buflen) {
- if(buflen <= sprintf(buf, "%4d", i)) { // 并不比以前好
- // ……现在情况如何呢?既然在这里问题被侦测出来了,那么这就
- // 意味着问题已经发生了,换句话说该被破坏的内存已经被破坏了
- }
- }
对于这个问题,根本没有解决方案。当错误被侦测出来时,内存已然被破坏,我们已经在不该写的地方写下了一些字节,如果情况糟糕的话,程序甚至根本没机会运行到报错代码处 。
议题#4:类型安全性。对于sprintf来说,类型错误就意味着运行时错误,而非编译期错误,更可怕的是这些类型错误甚至根本就不会表现出来。printf家族使用C的可变参数列表,C编译器通常并不检查这类实参列表的类型 。几乎每个C程序员都曾在一些微妙的或者不那么微妙的情况下发现他们搞错了格式字符串,这类错误总是再频繁不过地发生着,譬如在熬夜调试之后,试图重现某个关键客户遇到的神秘崩溃问题时。
诚然,示例2-1中的代码非常简单,只要清楚地知道我们只是将一个int传给sprintf,就可能足够简单地维护它。不过,即便如此,事情仍然可能出现纰漏,设想你的手指一不小心按错了键,这类情况并不罕见。例如,在大多数键盘上,c键跟d键是相邻的,所以我们可能一不小心把d错打成了c,结果就成了这样:
- sprintf(buf, "%4c", i); // 哦!
这会导致输出结果为字符而不是数字,这种情况下我们或许很快就能意识到错误所在,因为sprintf会一声不吭地将i的第一个字节解释为一个char值。此外,s键也跟d键相邻,因此如果我们错误地写成了:
- sprintf(buf, "%4s", i); // 糟糕!
如果情况是这样的话,或许我们同样能够很快反应过来,因为这么做很可能会令程序立即崩溃或至少偶发性地崩溃。因为这时sprintf会不加提示地将i解释为指向字符串的指针,并欣然地顺着这个指针所指的方向去寻找一个实际上并不存在的字符串,实际上,这个指针可能指向内存中的任何位置。
不过,下面这种情况可就微妙了,假设我们将d错打成了ld,会出现什么情况呢?
- sprintf(buf, "%4ld", i); // 一个微妙的错误
若是这种情况的话,给出的格式字符串就等于是在告诉sprintf,给它的是long int,而实际上给的却是int。这同样也是糟糕的C代码,不过,问题是它不仅不会以编译期错误的形式表现出来,甚至不会导致运行时错误。在许多流行的平台上,程序的运行结果仍然会跟以前一样,因为int在许多流行平台上碰巧跟long int具有相同的大小和内存布局。因而你也许一直都不会注意到这个潜在的问题,直到某一天将代码移植到某个平台上,该平台上的int跟long int具有不同的大小,这时才发现这个问题,甚至就连这个时候,程序可能也并不总是产生错误的结果或立即崩溃。
最后,考虑一个与此有关的问题。
议题#5:模板亲和性。很难将sprintf放在一个模板当中。考虑如下的代码:
- template<typename T>
- void PrettyFormat(T value, char* buf) {
- sprintf(buf, "%/* 这里应该写些什么呢?*/", value);
- }
你所能做到的最好的(最糟的?)就是声明一个主模板,并为所有那些与sprintf兼容的类型分别提供对应的特化版本:
- // 不算好点子: 一个东拼西凑出来的PrettyFormat
- //
- template<typename T>
- void PrettyFormat(T value, char* buf);
// 注意:主模板只有声明,没有定义 -
- template<> void PrettyFormat<int>(int value, char* buf) {
- sprintf(buf, "%d", value);
- }
- template<> void PrettyFormat<char>(char value, char* buf) {
- sprintf(buf, "%c", value);
- }
- //……还有其他特化版本,呃……
总的来说,sprintf是这样的:
|
< xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> |
sprintf |
|
标准吗
易用吗?代码清晰明确吗
高效吗?没有额外的内存分配吗
长度安全吗
类型安全吗
可用在模板当中吗 |
是: [C90], [C++(www.cppentry.com)03], [C99]
是
是
否
否
否 |
下一条我们将会考虑其他的解决方案,它们是在以上这些考虑因素之中进行取舍的结果。