之前在给字符数组分配堆空间时出现了堆访问越界的问题。
经过一番查找,发现是对字符串的strlen、sizeof和字符串末尾结束符没理解清楚,现在趁这个空隙捋一捋。
一.字符串读取默认是会以'\0'结尾的。
首先要知道strlen函数读出来的字符串长度是以在哪个位置上读取到'\0'作为字符串的终止标志的。
1.举例1:
本质为分配在常量区
int main()
{
char* s = "123\0 45";//在字符串中间人为插入'\0'
cout << strlen(s) << " " << sizeof(s) endl;
for (int i = 0; i < 8; ++i)cout << s[i] << endl;
return 0;
}
1) 观察输出结果,发现 strlen只读取了前三位1、2、3的长度,这说明strlen的读取规则是以第一次出现的'\0'作为字符串结尾的。
2)但是实际上为"123\0 45"这个字符串开辟了8个字节,分别存储的{ '1', '2' ,'3', '\0', '4', '5' , '\0'};
注意系统会为"123\0 45"这个字符串最后再多分配一个'\0'
3) 除此之外,还有一个需要注意到的:sizeof并不会正确读取这种情况下字符串的正确长度,只是读出一个字符指针的地址长度。
2.举例2:
本质为分配在栈区
int main()
{
char s2[5]{ '1','2','3','\0','5' };
cout << strlen(s2) << " " << sizeof(s2) << endl;
for (int i = 0; i < 5; ++i)cout << s2[i] << endl;
return 0;
}
以字符数组的形式分配字符串 ,strlen函数同样是以'\0'结尾作为结尾的。但是这时候的sizeof能正确读取到实际的字符数组的长度。
3.举例3:
本质为分配在堆区。
int main()
{
char* s3 = new char[6]{ '1','2','\0','3','4','5' };
cout << strlen(s3) << " " << sizeof(s3) << endl;
for (int i = 0; i < 5; ++i)cout << s3[i] << endl;
return 0;
}
同样,分配在堆区的情况strlen也是以读取'\0'作为结尾的。并且sizeof也只是读取到了字符指针的地址长度。
二、总结
如果要安全使用字符数组去表示字符串时,那么最后一位一定不要将其赋值,因为默认为字符数组分配的值便是'\0'。最后一位不赋值能让strlen函数正确读取到有效的字符串长度。
1.举例1:
int main()
{
char* s3 = new char[7]{ '1','2','3','4','5' };
cout << strlen(s3) << " " << sizeof(s3) << endl;
for (int i = 0; i < 5; ++i)cout << s3[i] << endl;
return 0;
}
数组在'5'后面没有赋值了,因此系统的默认分配是'\0'。因此读出的有效长度是5。
如果占用了最后一位的话呢?
int main()
{
char* s3 = new char[5]{ '1','2','3','4','5' };
cout << strlen(s3) << " " << sizeof(s3) << endl;
for (int i = 0; i < 5; ++i)cout << s3[i] << endl;
return 0;
}
这时候strlen读出来的长度是22,这可能是一个随机的值。如果我们采用的strlen读出的值作为数组长度那就会产生读取越界的问题了。
2.举例2
int main()
{
char s4[5]{ '1','2','3','4','5' };
cout << strlen(s4) << " " << sizeof(s4) << endl;
int n1 = strlen(s4);
for (int i = 0; i < n1; ++i)cout << s4[i] << endl;
}
分配在栈上的情况同理。
不正确地使用字符数组容易造成一些不易察觉的但是严重的问题,这是值得注意的。