场景
- 在现代
C++
开发指南出来后,并不建议使用C
的某些内存不安全的字符串处理函数。那么有哪些函数不安全?
说明
-
内存安全方面,肯定是要向
Rust
看齐的。使用标准std::string
字符串类,很大情况能避免缓冲区溢出问题。 -
如果旧项目里有用到以下的这些
C
字符串函数,那么最好用std::string
改写吧。-
strcpy
: 不要使用.strcpy
,目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。strcpy
最后会复制源缓冲区的\0
到目标缓冲区末尾,前提是目标缓冲器有足够的大小。 -
strcat
: 不要使用. 只是在目标缓冲区的0
位置开始添加新的字符串。目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。strcat
最后会复制源缓冲区的\0
到目标缓冲区末尾,前提是目标缓冲器有足够的大小。 -
sprintf
: 不要使用. 目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。sprintf最后会复制源缓冲区的
\0`到目标缓冲区末尾,前提是目标缓冲器有足够的大小。 -
strncpy
: 不要使用. 目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。·strncpy·最后不会复制一个结束符到目标缓冲区,如果目标缓冲区不是以0
结尾,那么在读取目标缓冲区时也会读取越界。 -
strncat
: 不要使用.strncat
目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。·strncat·最后会复制源缓冲区的\0
到目标缓冲区末尾,前提是目标缓冲器有足够的大小。
-
-
strcpy
,strcat
,strncpy
和strncat
可以使用std::string
代替;sprintf
可以使用snprintf
或者stringstream
代替。
例子
- 以下例子使用这些危险的
C
函数处理字符串,并使用std::string
和stringstream
来替换之。
#include <iostream>
#include <string.h>
#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>
using namespace std;
void TestUnsafeCStringFunc()
{
const int kBufSize = 32;
char buf[kBufSize] = {0};
string str;
// 30个字符
const char kStr[] = "http://blog.youkuaiyun.com/infoworld";
constexpr int kStrSize = sizeof(kStr) - 1;
// 1. 不要使用.`strcpy`,目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。
// -- `strcpy`最后会复制源缓冲区的`\0`到目标缓冲区末尾,前提是目标缓冲器有足够的大小。
strcpy(buf, kStr);
cout << "strcpy: " << buf << endl;
// -- 使用`string`的代替.
str.append(kStr);
cout << "string: " << str << endl;
// 2. 不要使用.同`strcpy`,只是在目标缓冲区的`0`位置开始添加新的字符串。目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。
// -- `strcat`最后会复制源缓冲区的`\0`到目标缓冲区末尾,前提是目标缓冲器有足够的大小。
memset(buf, 0, sizeof(buf));
strcat(buf, kStr);
strcat(buf, "/");
cout << "strcat: " << buf << endl;
// -- 使用`string`的代替.
str.clear();
str.append(kStr);
str.append("/");
cout << "string: " << str << endl;
// 3. 不要使用. 目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。
// -- `sprintf`最后会复制源缓冲区的`\0`到目标缓冲区末尾,前提是目标缓冲器有足够的大小。
sprintf(buf, "%d-%.2d-%.2d", 2025, 7, 22);
cout << "sprintf: " << buf << endl;
// -- 方法1:使用`stringstream`代替
stringstream ss;
ss << 2025 << "-" << std::setw(2) << std::setfill('0') << 7 << "-"<< std::setw(2) << std::setfill('0') << 22;
str = ss.str();
cout << "stringstream: " << str << endl;
// -- 方法2: 使用`snprintf`代替.
// -- 安全,指定目标缓冲区大小,最多指定目标缓冲区大小`-1`个字符被写入,之后会添加一个结束符。
// -- 如果`buf`为`NULL`,并且目标缓冲区大小为`0`时,返回写源字符串所需要的总大小, 不包括结束符。
snprintf(buf,sizeof(buf),"%d-%.2d-%.2d", 2025, 7, 22);
cout << "snprintf: " << buf << endl;
auto number = snprintf(NULL,0,"%d-%.2d-%.2d", 2025, 7, 22);
cout << "snprintf number: " << number << endl;
auto myBuf = (char*)malloc(number+1);
sprintf(myBuf,"%d-%.2d-%.2d", 2025, 7, 22);
cout << "myBuf: " << myBuf << endl;
free(myBuf);
// 4. 不要使用. `strncpy` 目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。
// -- ·strncpy·最后不会复制一个结束符到目标缓冲区,如果目标缓冲区不是以`0`结尾,那么在读取目标缓冲区时也会读取越界。
memset(buf, 0, sizeof(buf));
strncpy(buf, kStr, kStrSize);
cout << "strncpy: " << buf << endl;
// -- 同上,还是使用`std::string`代替。
// 5. 不要使用. `strncat` 目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。
// -- ·strncat·最后会复制源缓冲区的`\0`到目标缓冲区末尾,前提是目标缓冲器有足够的大小。
buf[kBufSize - 1] = '1'; // 测试最后添加一个`1`字符,调用`strncat`后会使用`0`把它覆盖。
strncat(buf, "/", 1);
cout << "strncat: " << buf << endl;
// -- 同上,还是使用`std::string`代替。
}
int main()
{
std::cout << "Hello World!\n";
std::cout << "=========== TestUnsafeCStringFunc =========!\n";
TestUnsafeCStringFunc();
}
输出
Hello World!
=========== TestUnsafeCStringFunc =========!
strcpy: http://blog.youkuaiyun.com/infoworld
string: http://blog.youkuaiyun.com/infoworld
strcat: http://blog.youkuaiyun.com/infoworld/
string: http://blog.youkuaiyun.com/infoworld/
sprintf: 2025-07-22
stringstream: 2025-07-22
snprintf: 2025-07-22
snprintf number: 10
myBuf: 2025-07-22
strncpy: http://blog.youkuaiyun.com/infoworld
strncat: http://blog.youkuaiyun.com/infoworld/