背景最近在RocketMQ社区开发Node.jsSDK,是基于RocketMQ的CSDK封装的Addon,CSDK是基于C++SDK封装的.但是,有个奇怪的问题,就是我在消费信息的时候,发现在macOS下得到的信息居然是乱码,也就是说在Linux下是正常的。要复现,我们首先要知道有一个函数是constchar*GetMessageTopic(CMessageExt*msg),用来从一个msg指针获取其Topic信息。乱码可以有好几个版本,是我调查的各种修改://将键名`topic`插入JavaScript`object`对象,值为`GetMessageTopic`//第一种方式写法:乱码Nan::Set(object,//v8中的JavaScript层对象Nan::New("topic").ToLocalChecked(),Nan::New(GetMessageTopic(msg)).ToLocalChecked());//另一种写法:乱码constchar*temp=GetMessageTopic(msg);Nan::Set(object,//v8中的JavaScript层对象Nan::New("topic").ToLocalChecked(),Nan::New(temp).ToLocalChecked());//第三种写法:乱码stringGetMessageColumn(CMessageExt*msg,char*name){//...constchar*orig=GetMessageTopic(msg);intlen=strlen(orig);炭温度[len+1];memcpy(temp,orig,sizeof(char)*(len+1));returntemp;}constchar*temp=GetMessageColumn(msg,"topic");Nan::Set(object,//v8中的JavaScript层对象Nan::New("topic").ToLocalChecked(),Nan::New(temp).ToLocalChecked());而且奇怪的是我调试第三种写法的时候发现constchar*orig=GetMessageTopic(msg);中orig的值是正确的这部分。并一步步运行,直到memcpy执行结束,orig内存块中的字符串莫名其妙的被修改成了乱码。参考如下:这下受不了了。当我坚持下来的时候,我发现当我改成这样的时候,返回值是正确的:intlen=strlen(orig);诠释我;炭温度[len+1];for(i=0;igetTopic().c_str();}让我们看一下这段代码。在GetMessageTopic中,我们首先得到一个getTopic的STL字符串,然后调用它的c_str()返回constchar*。一切看起来都很好,没问题。但是经过多次调试发现,同一个msg调用GetMessageTopic得到的指针其实是不一样的!我发现了一些新大陆吗?诚然,msg->getTopic()返回一个字符串对象,它来自m_topic通过复制构造。依稀记得大学时候看过的STL源码分析。根据STL字符串的Copy-On-Write,如果我不做任何改动,它们不应该同源吗?事实证明,我当时的“想当然”几乎让我无法找出问题所在。在我长期抓鸡毫无头绪后,从参考文献1中得到启发,开始脑洞大开(请原谅我找了半天这个坑,毕竟我的主力武器是还是Node.js),当前的String会不会是Copy-On-Write?但是在linux下是正常的。后来上网查了一下有没有人和我有同样的问题,终于找到了线索。不同的stl标准库有不同的实现。例如,CentOS6.5的默认stl::string实现是“Copy-On-Write”,而macOS(10.10.5)的实现是“Eager-Copy”。说白了,不同的库有不同的实现。Linux使用libstdc++,macOS使用libc++。在libc++的String实现中,并没有使用copy-on-write,赋值开始时使用的是深拷贝。也就是说,即使有两个相同的字符串,在两个不同的String对象中也不会同源。其实还有很多东西可以深挖。例如,《Effective STL》中的第15条也提到了String实现的多样性;大多数现代编译器还具有字符串中的短字符串优化功能;等等。回到乱码bug得到上面的结论之后,这个bug的原因就知道了。((MQMessageExt*)msg??)->getTopic()在函数中获取一个栈内存字符串变量。在Linux中,即使是栈内存变量,其c_str()仍然是指向源字符串的指针,所以函数声明循环结束,栈内存中的字符串被释放,c_str()指向的内存依然坚强;在macOS下,由于字符串是由栈内存分配的,而且字符串是深拷贝,所以c_str()的生命周期跟随字符串本身。一旦函数调用结束,字符串就被释放,相应的c_str()对应内存中的内容也被释放。综上所述,在macOS下,我通过GetMessageTopic()得到的内容其实是一个已经发布的地址。虽然for可以在复制之前抢救它的内存块,但是操作一个已经释放的内存块总是很危险的,因为它的内存块随时可能被覆盖,这就是前面乱码的本质。.更小的Demo验证针对STL在这两个平台上的不同行为,我也提取了一个最小化的Demo,大家可以在电脑上试一下:#include#includeusingnamespacestd;stringa="123";stringfunc1(){returna;}intmain(){printf("0x%.8X0x%.8X\n",a.c_str(),func1().c_str());return0;}上面的代码在linux(如ubuntu14.04)下运行会输出两个相同的指针地址,但在macOS下执行会输出两个不同的指针。总结在语言和库的使用中,我们不能使用一个没有在文档中明确定义行为的“特性”。例如,文档没有告诉你它使用了Copy-On-Write技术,这意味着它以后可能随时更改而不通知你,你也不容易找到。你只是使用定义的行为,也就是说,c_str()返回一个字符串的真实内容,我们不得不认为它遵循String的生命周期,即使它有黑科技在里面。毕竟下面是C++参考中提到的定义。我们不能假设其他人一定是COW行为:返回一个指向以null结尾的字符数组的指针,其数据与存储在字符串中的数据等效。指针的范围是[c_str();c_str()+size()]是有效的,其中的值对应于字符串中存储的值,在最后一个位置后附加一个空字符。这也可以扩展到JavaScript,比如早前在ECMAScript第三版262中定义一个对象时,对象中键名的顺序也是未定义的。即使随时更换设备,您也不能抱怨。好久没写文字了,编码能力变弱了。多于。参考《Why does calling c_str() on a function that returns a string not work?》《Why a new C++ Standard Library for C++11?》《Effective STL》#15:注意String实现的多样性《C++ 之 stl::string 写时拷贝导致的问题》《C++ 再探 String 之eager-copy、COW 和 SSO 方案》《C++ Short String Optimization stackoverflow 回答集锦以及我的思考》