2013-05-02 6 views
6

文字列値のプレースホルダとしてchar *(非const)を使用しているサードパーティのライブラリがあります。これらのデータ型に値を割り当てる正しい方法と安全な方法は何ですか?のは、それを警告するコンパイラを取り除く「安全な」方法は、およそコードになりますがchar *文字列を扱う正しい方法は何ですか?

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

#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; 

} 

出力:私は、実行時間を測定するために、自分自身のTimerクラスを使用して、次のテストのベンチマークを持っていますこのベンチマークによれば15-20倍遅くなります(反復ごとに1.9ナノ秒対反復ごとに31.7ナノ秒)。正しい方法とは何か、その "廃止された"方法については何が危険なのでしょうか?

+0

安全なケースで誰がメモリを解放しますか?正直言って、第三者図書館はひどく設計されています。 –

+2

一時バッファにコピーする場合は、少なくとも「ベクトル」を使用してください。 –

+1

* Aside *: 'new char [strlen(" teststr ")+ 1]'バッファの外側にNUL文字を書くのを避けるためです。 –

答えて

10

C++標準は明らかである。リテラル

通常の文字列は、(C++ 11でセクション2.14.5.8)「n個のconst charの配列」と入力しています。リテラル文字列を変更しようとする

効果は、(C++ 11でセクション2.14.5.12)未定義です。

コンパイル時に知られている文字列の場合は、non-const char*を得るための安全な方法は、あなたが安全に

char* ptr = literal; 

コンパイル時にあなたが文字列を知らない場合は、この

char literal[] = "teststring"; 

です長さを知っていれば配列を使うことができます:

char str[STR_LENGTH + 1]; 

len動的割り当てを使用する必要があります。文字列が不要になったときにメモリの割り当てを解除してください。

これは、APIがあなたが渡したchar*の所有権を取得しない場合にのみ機能します。

文字列を内部的に割り当てを解除しようとすると、ドキュメント内でその文字列を割り当てて、文字列を割り当てる適切な方法を知らせる必要があります。割り当て方法は、APIによって内部的に使用されているものと一致させる必要があります。

char literal[] = "test"; 

automatinc記憶(実行変数が宣言された範囲を離れるときに破壊される変数を意味する)とローカル、5文字列を作成し、アレイ内の各文字を初期化します't'、 'e'、 's'、 't'、 '\ 0'の文字列を使用します。

あなたは、後でこれらの文字を編集することができます:literal[2] = 'x';

あなたがこれを書いている場合:

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

を、その後、コンパイラによっては、str1str2同じ値(すなわち、ポイントになることがあり同じ文字列)。

(「すべての文字列リテラルが区別できる(つまり、重複しないオブジェクトに格納されているかどうか)は実装定義です」を参照してください。012 C++標準の12)

これらはメモリの読み取り専用セクションに格納されているため、文字列を変更しようとすると例外/クラッシュが発生する可能性があります。

彼らはタイプconst char*の現実にもあるので、この行:* STR = "テスト"

のchar。

は、文字列のconst値を実際にキャストします。そのため、コンパイラは警告を発行します。

+0

非常に良い答え! {char literal [] = "teststring"の違いは何ですか? }と{char * literal = "teststring"; }?前者は少なくともコンパイラの警告を出すわけではありません。後者は文字列(文字の配列)をヒープに割り当て、前者はそれをスタックに代入しますか? –

+1

@seb私の更新された回答を参照してください – Andrei

+0

So {char literal [] = "test"; }文字配列をローカルに(スタックに)割り当てます。もし私が{char * str = literal; }その後、スタックにあるこのchar配列のアドレスにポインタを渡しますか?それが本当であれば、 'literal'がスコープから外れると 'str'はメモリ内の定義されていない空間を指していますか? –

5

安全でない方法は、コンパイル時に知られているすべての文字列を検索する方法です。

あなたの「安全な」方法ではメモリがリークし、かなり恐ろしいものです。

通常、const char *を受け入れる正常なC APIを使用するので、C++で適切な安全な方法、つまりstd::stringとそのc_str()メソッドを使用できます。あなたのCのAPIは、文字列の所有権を前提とした場合

、あなたの「安全な方法は、」別の欠陥を持っている:あなたが渡して、new[]free()を混在させることはできませんメモリ上free()を呼び出すことを期待C APIにC++ new[]演算子を使用して割り当てそれは許可されていません。 C APIが文字列の後にfree()を呼び出さないようにするには、C++側でnew[]を使用するのがよいでしょう。

また、これはあなたがここにC文字列に関する根本的な誤解があるようだC++とC

+5

彼は実際には 'std :: string :: c_str()'を使用することはできません。なぜなら、APIがnon-constを望んでいるからです。 –

+2

APIがちょうど良く書かれていない場合、const_castがそうです。 –

+0

@SebastianRedl彼のプログラムでUBを作成するかもしれません。 –

4

の奇妙な混合物です。

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

ここでは、文字列定数にポインタを割り当てるだけです。 CおよびC++では、文字列リテラルの型はchar[N]であり、配列 "decay"のために文字列リテラル配列へのポインタを割り当てることができます。しかし、非定数ポインタを文字列リテラルに代入することは推奨されません。

しかし、文字列リテラルへのポインタの割り当ては、実行することができません。 APIでconst以外の文字列が必要です。文字列リテラルはconstです。

[char * strings]に値を割り当てる正しい方法はありますか?

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

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

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

+0

最初のメソッドが安全でないかどうかはわかりませんが、コンパイラの警告が表示されます:文字列リテラルから 'char *廃止予定[-Wdeprecated-writable-strings] ...この警告を心配する理由はありますか? –

+1

@sebこれらの文字列は実際には 'const char'の配列であり、それらを変更すると未定義の動作につながるので非推奨です。私の反応を見てください。 – Andrei

関連する問題