记录 Java 转 C++ 开发时遇到的那些“坑”(二)

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
相关推荐
xxwxx__16 分钟前
C++ STL set 与 map 全套详解:关联式容器、红黑树底层、代码坑点、刷题实战
数据结构·c++
j7~2 小时前
【Linux网络加餐】(篇六)网络版计算器(上):模板方法模式、序列化与 JSON
linux·c++·json·序列化·反序列化·网络版计算机
weixin_307779132 小时前
C++代码实现MATLAB中的ode45函数功能
开发语言·c++·算法·matlab
张小姐的猫2 小时前
【AI大模型接入SDK】 —— 数据管理 & 与Session模块进行联动
数据结构·数据库·c++·人工智能·python·chatgpt
東隅已逝,桑榆非晚3 小时前
vector(模拟实现)
c++·笔记·学习
蒸蒸yyyyzwd3 小时前
cpp 选手备战秋招学习笔记 day36
c++
用户69586106727113 小时前
从源码到加载器:__attribute__((constructor)) / __attribute__((destructor))的跨平台实现原理详解
c++
余额瞒着我当琳5 小时前
C++多态深入解析:多态概念,多态实现条件,虚函数表与内存分布,常见问题及注意事项
c++·算法
ocean21035 小时前
2025-2026年C++专题大厂面试高频问题
开发语言·c++·面试
程序猿编码5 小时前
扔掉 Python!纯 C++ 本地跑扩散 TTS,GGML 加持,支持 RK3588 NPU 加速
c++·计算机视觉·大模型·transformer·rk3588·推理·ggml