[C/C++内存安全]_[中级]_[安全处理字符串]
场景
- 在现代
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.csdn.net/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.csdn.net/infoworld
string: http://blog.csdn.net/infoworld
strcat: http://blog.csdn.net/infoworld/
string: http://blog.csdn.net/infoworld/
sprintf: 2025-07-22
stringstream: 2025-07-22
snprintf: 2025-07-22
snprintf number: 10
myBuf: 2025-07-22
strncpy: http://blog.csdn.net/infoworld
strncat: http://blog.csdn.net/infoworld/
参考
-
strcpy
-
strcat
-
strncpy
-
strncat
-
sprintf,snprintf
-
stringstream
-
string
-
C++11语言特性和标准库
-
如何实现std::string自己的Format(sprintf)函数
-
避免使用wsprintf函数
-
setw
-
setfill