1. 将 string 内容拷贝到 char 数组时的注意事项
假设有一天你得到了一个 char 数组,你想把它保存在本地缓存里,于是你想到可以对它进行base64编码,等需要的时候再从缓存文件里读出来,对其进行 base64 解码后,再转成 char 数组。
demo 代码如下:
c++
char key[8] = {1, 2, 3, 4, 5, 6, 7, 8};
string encodeStr = base64_encode(key, sizeof(key));
cout << "encode =" << encodeStr << endl;//打印下base64编码后的key
string decodeStr = base64_decode(encodeStr);
char tmpKey[8] = {0};
strcpy(tmpKey, decodeStr.c_str());
cout << "encode again=" << base64_encode(tmpKey, sizeof(tmpKey)) << endl;//打印下base64编码后的tmpKey
输出结果:
ini
encode =AQIDBAUGBwg=
encode again=AQIDBAUGBwg=
两次编码的结果一致,没问题。
现在改下原始的 char 数组,将第四个元素改成0:
c++
char key[8] = {1, 2, 3, 0, 5, 6, 7, 8};
string encodeStr = base64_encode(key, sizeof(key));
cout << "encode =" << encodeStr << endl;//打印下base64编码后的key
string decodeStr = base64_decode(encodeStr);
char tmpKey[8] = {0};
strcpy(tmpKey, decodeStr.c_str());
cout << "encode again=" << base64_encode(tmpKey, sizeof(tmpKey)) << endl;//打印下base64编码后的tmpKey
执行结果:
ini
encode =AQIDAAUGBwg=
encode again=AQIDAAAAAAA=
为什么只是改了一个字符,两次编码的结果反而不相等了呢?
聪明的你应该已经知道原因了,如果一个 string 中间有\0, 那么 c_str()
得到的值其实会做截断。根本原因是 C 语言的字符串实际上是以\0
结尾的字符数组。
道理都懂,但是开发的时候稍不注意,这个特性就会在关键的时候背刺你。在我当时参与的业务场景里,这个 char 数组是由另一个 sdk 生成的,大部分情况下生的 char 数组里是没有\0
的。一旦出问题,很难排查。
解决办法:改用 std:copy 或者 memcpy :
c++
char key[8] = {1, 2, 3, 0, 5, 6, 7, 8};
string encodeStr = base64_encode(key, sizeof(key));
cout << "encode =" << encodeStr << endl;
string decodeStr = base64_decode(encodeStr);
char tmpKey[8] = {0};
std::copy(decodeStr.begin(), decodeStr.end(), tmpKey);
cout << "encode again=" << base64_encode(tmpKey, sizeof(tmpKey)) << endl;
2. mutex 是不可重入的
c
mutex mutex_;
void demo_mutex1(){
std::lock_guard<std::mutex> lock1(mutex_);
std::cout << "Hello, World!" << std::endl;
std::lock_guard<std::mutex> lock2(mutex_);
std::cout << "Hello, World!" << std::endl;
}
上述代码会卡住,只打印1次。因为mutex是不可重入的。所以使用时要尤其注意,以防出现死锁。
解决办法1:用代码块。这样 lock_guard 就会在代码作用域结束时,释放所持有的锁。
c
void demo_mutex2(){
std::cout << "Hello, World!" << std::endl;
{
std::lock_guard<std::mutex> lock(mutex_);
std::cout << "Hello, World!" << std::endl;
}
{
std::lock_guard<std::mutex> lock(mutex_);
std::cout << "Hello, World!" << std::endl;
}
}
解决办法2:使用可重入锁 recursive_mutex:
c
void demo_mutex3(){
std::lock_guard<std::recursive_mutex> lock1(recursive_mutex_);
std::cout << "Hello, World!" << std::endl;
std::lock_guard<std::recursive_mutex> lock2(recursive_mutex_);
std::cout << "Hello, World!" << std::endl;
}
3. lambda 函数里尝试打印函数名时的一个注意事项
日常开发中一个常见做法是,利用 __FUNCTION__
获取当前所在的函数名,然后在日志里打印。不过如果日志代码是在 lambda 函数里,__FUNCTION__
就会失效。 例如:
c
void demo_callback() {
std::cout << "[" << __FUNCTION__ << "]" << "begin " << std::endl;
getFileMd5("./1.zip", [](const string filePath, const string md5) {
std::cout << "[" << __FUNCTION__ << "]" << "callback begin"<< std::endl;
std::cout << "[" << __FUNCTION__ << "]" << "filePath= " << filePath << ", md5=" << md5 << std::endl;
});
}
打印结果会是:
ini
[demo_callback]begin
[operator()]callback begin
[operator()]filePath= ./1.zip, md5=0a762bb0109079550889a443c7b67769
看来lambda函数里的__FUNCTION__
只能拿到"operator()"
,无法获取外层的真正函数名。
于是我打算在 lambda 函数外把__FUNCTION__
暂时存起来,然后再传递给lambda函数内部。
上图中的"Capture 'funcName' by reference"
可是CLion给的提示哦,应该不会错吧。点击之后,代码里会自动添加一句&funcName
。
于是就有了下面的代码:计算文件的 md5,通过回调的方式返回。
c
void getFileMd5(const string filePath, CallbackFunction callback) {
string funcName = __FUNCTION__;
std::thread th([filePath, callback, &funcName]() {
cout << "[" << funcName << "]" << "thread begin"<< endl;
MD5 md5 = MD5();
auto md5Chars = md5.digestFile(filePath.c_str());
string md5Str = string(md5Chars);
cout << "[" << funcName << "]" << "md5Str=" << md5Str << endl;
callback(filePath, md5Str);
});
th.detach();
}
void demo_callback() {
std::cout << "[" << __FUNCTION__ << "]" << "begin " << std::endl;
string funcName = __FUNCTION__;
getFileMd5("./1.zip", [&funcName](const string filePath, const string md5) {
std::cout << "[" << funcName << "]" << "callback begin" << std::endl;
std::cout << "[" << funcName << "]" << "filePath= " << filePath << ", md5=" << md5 << std::endl;
});
}
运行结果:
ini
[demo_callback]begin
[]thread begin
[]md5Str=40178e0d50fa8f67f154b4f7c740cf4c
[]callback begin
[]filePath= ./1.zip, md5=40178e0d50fa8f67f154b4f7c740cf4c
奇怪,为什么打印出来的 funcName 会是空字符串呢?
最后我发现,罪魁祸首就是这个 CLion 自动生成的&funcName
,解决办法是去掉&
,即从引用传递改成值传递。
原因是,在 demo_callback 和 getFileMd5 这两个函数里,lambda 函数的生命周期都比变量 funcName 的生命周期要长。当在lambda 函数里尝试访问 funcName 时,funcName 引用指向的对象其实已经被回收了,这样一来就会出现"引用悬空"
,可能导致难以预期的结果,甚至可能出现乱码。
纠正后的代码:
c
void getFileMd5(const string filePath, CallbackFunction callback) {
string funcName = __FUNCTION__;
std::thread th([filePath, callback, funcName]() {
cout << "[" << funcName << "]" << "thread begin"<< endl;
MD5 md5 = MD5();
auto md5Chars = md5.digestFile(filePath.c_str());
string md5Str = string(md5Chars);
cout << "[" << funcName << "]" << "md5Str=" << md5Str << endl;
callback(filePath, md5Str);
});
th.detach();
}
void demo_callback() {
std::cout << "[" << __FUNCTION__ << "]" << "begin " << std::endl;
string funcName = __FUNCTION__;
getFileMd5("./1.zip", [funcName](const string filePath, const string md5) {
std::cout << "[" << funcName << "]" << "callback begin" << std::endl;
std::cout << "[" << funcName << "]" << "filePath= " << filePath << ", md5=" << md5 << std::endl;
});
}
输出结果:
ini
[demo_callback]begin
[getFileMd5]thread begin
[getFileMd5]md5Str=d41d8cd98f00b204e9800998ecf8427e
[demo_callback]callback begin
[demo_callback]filePath= ./1.zip, md5=d41d8cd98f00b204e9800998ecf8427e