7

我有一个第三方库,它使用 char* (非常量)作为字符串值的占位符。将值分配给这些数据类型的正确且安全的方法是什么?我有以下测试基准,它使用我自己的计时器类来测量执行时间:

#include "string.h"
#include <iostream>
#include <sj/timer_chrono.hpp>

using namespace std;

int main()
{
    sj::timer_chrono sw;

    int iterations = 1e7;

    // first method gives compiler warning:
    // conversion from string literal to 'char *' is deprecated [-Wdeprecated-writable-strings]
    cout << "creating c-strings unsafe(?) way..." << endl;
    sw.start();
    for (int i = 0; i < iterations; ++i)
    {
        char* str = "teststring";
    }   
    sw.stop();
    cout << sw.elapsed_ns() / (double)iterations << " ns" << endl;

    cout << "creating c-strings safe(?) way..." << endl;
    sw.start();
    for (int i = 0; i < iterations; ++i)
    {
        char* str = new char[strlen("teststr")];
        strcpy(str, "teststring");
    }   
    sw.stop();
    cout << sw.elapsed_ns() / (double)iterations << " ns" << endl;


    return 0;

}

输出:

creating c-strings unsafe(?) way...
1.9164 ns
creating c-strings safe(?) way...
31.7406 ns

虽然“安全”方式摆脱了编译器警告,但根据这个基准,它使代码慢了大约 15-20 倍(每次迭代 1.9 纳秒 vs 每次迭代 31.7 纳秒)。什么是正确的方法,这种“已弃用”的方法有什么危险?

4

3 回答 3

10

C++ 标准很明确:

普通字符串文字的类型为“n const char 数组”(C++11 中的第 2.14.5.8 节)。

尝试修改字符串文字的效果是未定义的(C++11 中的第 2.14.5.12 节)。

对于在编译时已知的字符串,获取 a 的安全方法non-const char*是这样

char literal[] = "teststring";

然后你可以安全地

char* ptr = literal;

如果在编译时您不知道字符串但知道其长度,则可以使用数组:

char str[STR_LENGTH + 1];

如果您不知道长度,那么您将需要使用动态分配。确保在不再需要字符串时释放内存。

这仅在 API 不拥有char*您传递的所有权时才有效。

如果它试图在内部释放字符串,那么它应该在文档中说明并告知您分配字符串的正确方法。您需要将分配方法与 API 内部使用的分配方法相匹配。

char literal[] = "test";

将创建一个带有 automatinc 存储的本地 5 字符数组(意味着当执行离开声明变量的范围时,该变量将被销毁)并使用字符 't'、'e'、' 初始化数组中的每个字符s'、't' 和 '\0'。

您可以稍后编辑这些字符:literal[2] = 'x';

如果你这样写:

char* str1 = "test";
char* str2 = "test";

然后,取决于编译器,str1可能str2相同的值(即指向相同的字符串)。

(“是否所有字符串文字都是不同的(即,是否存储在不重叠的对象中)是实现定义的。”在 C++ 标准的第 2.14.5.12 节中)

它们也可能存储在内存的只读部分中,因此任何修改字符串的尝试都将导致异常/崩溃。

它们实际上也是const char*这样的类型:

char* str = "测试";

实际上抛弃了字符串上的 const-ness,这就是编译器会发出警告的原因。

于 2013-05-02T13:38:49.180 回答
5

不安全的方法是用于编译时已知的所有字符串。

您的“安全”方式会泄漏内存并且非常可怕。

通常你会有一个理智的 C API 接受const char *,所以你可以在 C++ 中使用适当的安全方式,即std::string及其c_str()方法。

如果您的 C API 假定字符串的所有权,那么您的“安全方式”还有另一个缺陷:您不能混合使用new[]and free(),将使用 C++ 运算符分配的内存传递给new[]期望调用free()它的 C API 是不允许的。如果 C API 不想free()稍后在字符串上调用,那么new[]在 C++ 端使用应该没问题。

此外,这是 C++ 和 C 的奇怪混合。

于 2013-05-02T13:07:13.453 回答
4

您似乎对这里的 C 字符串有一个基本的误解。

cout << "creating c-strings unsafe(?) way..." << endl;
sw.start();
for (int i = 0; i < iterations; ++i)
{
    char* str = "teststring";
} 

在这里,您只是分配一个指向字符串文字常量的指针。在 C 和 C++ 中,字符串文字的类型为char[N],由于数组“衰减”,您可以将指针分配给字符串文字数组。(但是,不推荐将非常量指针分配给字符串文字。)

但是分配一个指向字符串文字的指针不是你想要做的。您的 API 需要一个非常量字符串。字符串文字是const.

将值分配给这些 [char* 字符串] 的正确且安全的方法是什么?

这个问题没有一般的答案。每当您使用 C 字符串(或一般的指针)时,您都需要处理所有权的概念。C++ 使用std::string. 在内部,std::string拥有一个指向char*数组的指针,但它为您管理内存,因此您无需关心它。但是当您使用原始 C 字符串时,您确实需要考虑管理内存。

您如何管理内存取决于您对程序所做的工作。如果你用 分配一个 C 字符串new[],那么你需要用 释放它delete[]。如果您使用 分配它malloc,那么您必须使用 取消分配它free()。在 C++ 中使用 C 字符串的一个很好的解决方案是使用一个智能指针,它拥有分配的 C 字符串的所有权。(但您需要使用deleter释放内存的delete[])。或者你可以只使用std::vector<char>. 与往常一样,不要忘记为终止的空字符分配空间。

此外,您的第二个循环慢得多的原因是因为它在每次迭代中分配内存,而第一个循环只是将指针分配给静态分配的字符串文字。

于 2013-05-02T13:17:47.063 回答