处理char *字符串的正确方法是什么?

时间:2013-05-02 13:05:10

标签: c++

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

#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纳秒,每次迭代31.7纳秒)。什么是正确的方式,以及那种“弃用”的方式是如此危险?

3 个答案:

答案 0 :(得分:10)

C ++标准很明确:

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

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

对于编译时已知的字符串,获取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";

然后,根据编译器,str1str2可能是相同的值(即,指向相同的字符串)。

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

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

它们实际上也是类型const char*所以这一行:

char * str =“test”;

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

答案 1 :(得分:5)

不安全的方法是在编译时知道所有字符串的方法。

你的“安全”方式会泄漏记忆,而且非常可怕。

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

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

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

答案 2 :(得分: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字符串。字符串文字是const

  

为那些[char * strings]分配值的正确和安全的方法是什么?

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

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

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