Wednesday, October 13, 2010

c风格字符串的疑问

最近再看c++ primer,比本科时候看的时候体会要深得多,以前看来真的是打酱油的。
看到c风格字符串的时候,有了几个疑问,如下:
程序 1 如下:
#include
#include
#include

using namespace std;
int main(int argc, char *argv[])
{
const char ca[] = {'h', 'e', 'l', 'l', 'o'};
cout << strlen(ca) << endl;
int i = 0;
while (ca[i] != '\0') {
cout << ca[i++] << endl;
}
cout << strlen(ca) << endl;
}
windows xp下的mingw 结果
D:\c_c_plus>a.exe
5
h
e
l
l
o

6
当时我非常纳闷怎么ca的长度会变化了(5,6),试了几次结果都是这样,没有想明白,于是换到了linux下同样的程序,结果如下
sulong@sulong-desktop:~/Documents/c_c_plus$ ./a.out 
8
h
e
l
l
o

6
这里,ca的长度再次变化了(5,8),我的第一反应是在此好像strlen对ca不起作用了,仔细看书,归结出原因在此:
  • strlen等标准库函数的参数是c风格字符串(c++ primer中描述到,传递给这些函数的指针必须具有非零值,而且指向以null结束的字符数组中的第一个元素); 
  • 而c风格字符串有一点值得注意,需要以null结束,如char ca1[] = {'1', '2'}就不是,而char ca2={'1', '2', '\0'}则是,同时字符串字面量也是c风格字符串的实例; 
  • strlen总是假定其参数字符串以null字符结束,当调用该函数时,系统将会从实参指向的内存空间开始一致搜索结束符,知道恰好遇到null位置,strlen返回的这一段内存空间内总共有多少个字符; 
  • 当实参是非c风格字符的时候,这个数值是不可预知的;
了解原因之后,修改程序,程序 2 和结果如下(windows和linux都正确,这里只列出linux的):
#include
#include
#include

using namespace std;
int main(int argc, char *argv[])
{
const char ca[] = {'h', 'e', 'l', 'l', 'o', '\0'};
cout << strlen(ca) << endl;
int i = 0;
while (ca[i] != '\0') {
cout << ca[i++] << endl;
}
cout << strlen(ca) << endl;
}

output:
5
h
e
l
l
o
5
不过有一点不是很明白,对于第一个程序,为什么对ca进行解引用之后,ca的长度变化了?windows(5,6),linux(8,6),我的猜测是c++允许计算数组的超出末端的地址,但是不允许对此地址进行解引用操作,否则结果是未定义的。
除了strlen函数,我同时测试了其他cstring中的库函数,参数必须严格安装说明和建议,否则结果也是未定义的,如下 程序 3 :
#include
#include

using namespace std;

int main(int argc, char* argv[])
{
const char *c1 = "hello";
const char *c2 = "world";
char pc[5 + 5 + 1];
strncpy(pc, c1, 5);

cout << pc << endl;
}

output:
hello6
因为strncpy(pc, c1, 5)的5只够存储hello,而null字符也是需要空间的,使用的时候,时刻记住一定要算上结束符null,需要修改为6以上(当然不能超过pc的size),结果才能正确输出为hello。

两点感悟:
1. 少用c风格字符串,用string
2. 多多编程测试细节

1 comment: