在我们的C++代码中,我们有自己的字符串类(出于遗留原因).它支持一种c_str()
类似的方法std::string
.我注意到很多开发人员都错误地使用它.我已将问题减少到以下行:
const char* x = std::string("abc").c_str();
Run Code Online (Sandbox Code Playgroud)
这个看似无辜的代码非常危险,因为析构函数std::string
会在调用之后立即被调用c_str()
.因此,您将持有指向取消分配的内存位置的指针.
这是另一个例子:
std::string x("abc");
const char* y = x.substr(0,1).c_str();
Run Code Online (Sandbox Code Playgroud)
在这里,我们使用指向解除分配位置的指针.
这些问题在测试期间不容易找到,因为内存位置仍包含有效数据(尽管内存位置本身无效).
我想知道你是否有任何关于如何修改类/方法定义的建议,以便开发人员永远不会犯这样的错误.
代码的现代部分不应该处理像这样的原始指针.c_str
仅在为遗留函数提供参数时调用const char*
.喜欢:
legacy_print(x.substr(0,1).c_str())
Run Code Online (Sandbox Code Playgroud)
为什么要创建类型的局部变量const char*
?即使您编写复制版本, c_str_copy()
您也会感到头疼,因为现在客户端代码负责删除生成的指针.
如果您需要将数据保留更长时间(例如,因为您希望将数据传递给多个遗留函数),那么只需将数据保存在字符串实例中.
对于基本情况,您可以在"this"对象上添加ref限定符,以确保永远不会立即调用.c_str().当然,这不能阻止它们存储在指针之前留下范围的变量中.
const char *c_str() & { return ...; }
Run Code Online (Sandbox Code Playgroud)
但更大的解决方案是将所有函数替换为在代码库中使用"const char*"和带有一个字符串类的函数(至少需要两个:拥有字符串和借用的片) - 并确保不能从"const char*"隐式构造任何字符串类.
最简单的解决方案是更改析构函数,以便在销毁时在字符串的开头写入null.(或者,使用错误消息或0填充整个字符串;您可以使用一个标志来禁用此版本的发布代码.)
虽然它并没有直接阻止程序员犯下使用无效指针的错误,但是当代码没有做它应该做的事情时,肯定会引起注意.这应该可以帮助您清除代码中的问题.
(正如你所提到的,目前错误被忽视,因为在大多数情况下,代码将很乐意使用无效内存运行.)
我不确定如果你警告人们错误地使用你的图书馆,你能做些什么。考虑实际的 stlstring
库。如果我这样做:
const char * lala = std::string("lala").c_str();
std::cout << lala << std::endl;
const char * lala2 = std::string("lalb").c_str();
std::cout << lala << std::endl;
std::cout << lala2 << std::endl;
Run Code Online (Sandbox Code Playgroud)
我基本上是在创造未定义的行为。在我在 ideone.com 上运行它的情况下,我得到以下输出:
lala
lalb
lalb
Run Code Online (Sandbox Code Playgroud)
很明显原来的记忆lala
已经被覆盖了。我只是在文档中向用户明确说明这种编码是不好的做法。