[C/C++内存安全]_[中级]_[安全处理字符串]

场景

  1. 在现代C++开发指南出来后,并不建议使用C的某些内存不安全的字符串处理函数。那么有哪些函数不安全?

说明

  1. 内存安全方面,肯定是要向Rust看齐的。使用标准std::string字符串类,很大情况能避免缓冲区溢出问题。

  2. 如果旧项目里有用到以下的这些C字符串函数,那么最好用std::string改写吧。

    • strcpy: 不要使用.strcpy,目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。strcpy最后会复制源缓冲区的\0到目标缓冲区末尾,前提是目标缓冲器有足够的大小。

    • strcat: 不要使用. 只是在目标缓冲区的0位置开始添加新的字符串。目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。strcat最后会复制源缓冲区的\0到目标缓冲区末尾,前提是目标缓冲器有足够的大小。

    • sprintf: 不要使用. 目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。sprintf最后会复制源缓冲区的\0`到目标缓冲区末尾,前提是目标缓冲器有足够的大小。

    • strncpy: 不要使用. 目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。·strncpy·最后不会复制一个结束符到目标缓冲区,如果目标缓冲区不是以0结尾,那么在读取目标缓冲区时也会读取越界。

    • strncat: 不要使用.strncat 目标缓冲区没有设置最大接收的大小,容易造成缓冲区溢出。·strncat·最后会复制源缓冲区的\0到目标缓冲区末尾,前提是目标缓冲器有足够的大小。

  3. strcpy,strcat,strncpystrncat可以使用std::string代替;sprintf可以使用snprintf或者stringstream代替。

例子

  1. 以下例子使用这些危险的C函数处理字符串,并使用std::stringstringstream来替换之。
#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/

参考

  1. strcpy

  2. strcat

  3. strncpy

  4. strncat

  5. sprintf,snprintf

  6. stringstream

  7. string

  8. C++11语言特性和标准库

  9. 如何实现std::string自己的Format(sprintf)函数

  10. 避免使用wsprintf函数

  11. setw

  12. setfill

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter(阿斯拉达)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值