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 ナノ秒対反復あたり 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";

自動ストレージを使用してローカルの 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 に答える
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]は であり、配列の "減衰" により、文字列リテラル配列へのポインターを割り当てることができます。(ただし、非 const ポインターを文字列リテラルに割り当てることは非推奨です。)

しかし、文字列リテラルへのポインターの割り当ては、あなたがやりたいことではありません。API は非 const 文字列を想定しています。文字列リテラルはconst.

これらの [char* 文字列] に値を割り当てる正しい安全な方法は何ですか?

この質問に対する一般的な答えはありません。C の文字列 (または一般的なポインター) を扱うときはいつでも、所有権の概念に対処する必要があります。C++ はこれを自動的に処理しstd::stringます。内部的にstd::stringは、配列へのポインタを所有していますchar*が、メモリを管理するので、気にする必要はありません。ただし、生の C 文字列を使用する場合は、メモリの管理を考慮する必要があります。

メモリを管理する方法は、プログラムで何をしているかによって異なります。で C 文字列を割り当てた場合は、new[]で割り当てを解除する必要がありますdelete[]。で割り当てた場合はmalloc、 で割り当てを解除する必要がありますfree()。C++ で C 文字列を操作するための適切な解決策は、割り当てられた C 文字列の所有権を取得するスマート ポインターを使用することです。deleter(ただし、 でメモリの割り当てを解除する を使用する必要がありますdelete[])。または、そのまま使用できますstd::vector<char>。いつものように、終端のヌル文字のためのスペースを確保することを忘れないでください。

また、2 番目のループが非常に遅い理由は、1 番目のループが静的に割り当てられた文字列リテラルにポインターを割り当てるだけであるのに対し、各反復でメモリを割り当てるためです。

于 2013-05-02T13:17:47.063 に答える