文字列定数から 'char *'への非推奨の変換


回答:


26

私の不思議なように、このエラーの理由と理由について、少しの背景技術情報を提供します。

C文字列を初期化する4つの異なる方法を調べ、それらの違いを確認します。これらは問題の4つの方法です。

char *text = "This is some text";
char text[] = "This is some text";
const char *text = "This is some text";
const char text[] = "This is some text";

このために、3番目の文字「i」を「o」に変更して、「Thos is some text」にします。それは、すべての場合(あなたが思うだろうが)、次のようにして達成できます。

text[2] = 'o';

それでは、文字列を宣言する各方法が何をするのか、そしてそのtext[2] = 'o';ステートメントが物事にどのように影響するのかを見てみましょう。

最初に最もよく見られる方法:char *text = "This is some text";。これは文字通りどういう意味ですか?さて、Cでは、文字通り「text読み取り専用(コード)スペースに保持されているこの文字列リテラルへの読み取り/書き込みポインターである変数を作成する」という意味です。オプションを-Wwrite-stringsオンにしている場合は、上記の質問に示されているように警告が表示されます。

基本的には、「警告:書き込みできない領域への読み取り/書き込みポイントである変数を作成しようとしました」という意味です。3番目の文字を「o」に設定してみた場合、実際には読み取り専用の領域に書き込もうとしているので、うまくいきません。Linuxを搭載した従来のPCの場合:

セグメンテーション障害

次に、2つ目:char text[] = "This is some text";。文字通り、Cでは、「型「char」の配列を作成し、「This is some text \ 0」データで初期化します。配列のサイズは、データを格納するのに十分な大きさです」。そのため、実際にRAMが割り当てられ、実行時に値 "This is some text \ 0"がRAMにコピーされます。警告なし、エラーなし、完全に有効。そして、あなたがデータを編集できるようにしたい場合、それを行う正しい方法。コマンドを実行してみましょうtext[2] = 'o'

Thosはテキストです

完璧に機能しました。良い。

次に、3番目の方法:const char *text = "This is some text";。再び文字通りの意味:「読み取り専用メモリ内のこのデータへの読み取り専用ポインタである「テキスト」と呼ばれる変数を作成します。」ポインターとデータの両方が読み取り専用になっていることに注意してください。エラーも警告もありません。テストコマンドを実行しようとするとどうなりますか?できません。コンパイラはインテリジェントになり、悪いことをしようとしていることがわかります。

エラー:読み取り専用の場所 '*(text + 2u)'の割り当て

コンパイルすらしません。読み取り専用メモリへのポインタの書き込みをコンパイラに伝えているため、読み取り専用メモリへの書き込みは保護されます。もちろん、読み取り専用メモリを指す必要はありません、読み取り/書き込みメモリ(RAM)を指す場合、そのメモリはコンパイラによる書き込みから保護されます。

最後の最後のフォーム:const char text[] = "This is some text";。繰り返しますが、以前と同様に[]、RAMに配列を割り当て、そこにデータをコピーします。ただし、これは読み取り専用配列です。それへのポインタはとしてタグ付けされているので、あなたはそれに書き込むことができませんconst。書き込みを試みると、次の結果になります。

エラー:読み取り専用の場所 '*(text + 2u)'の割り当て

だから、私たちがどこにいるかの簡単な要約:

このフォームは完全に無効であり、いかなる場合でも回避する必要があります。それはあらゆる種類の悪い出来事への扉を開きます:

char *text = "This is some text";

データを編集可能にする場合、このフォームは適切なフォームです。

char text[] = "This is some text";

編集されない文字列が必要な場合、このフォームは正しいフォームです。

const char *text = "This is some text";

この形式はRAMを無駄にしているように見えますが、その用途はあります。今のところそれを忘れてください。

const char text[] = "This is some text";

6
Arduinos(少なくともAVRベースのもの)では、文字列リテラルはPROGMEMPSTR()やなどのマクロで宣言しない限り、RAMに存在することに注意してくださいF()。したがって、をconst char text[]超えるRAMは使用しませんconst char *text
エドガーボネット

Teensyduinoやその他の多くの最近のarduino互換機能は、コードスペースに文字列リテラルを自動的に配置するため、ボードでF()が必要かどうかを確認する価値があります。
クレイグ。

@ Craig.Feied一般に、F()は関係なく使用する必要があります。それを「必要としない」人は、それを単純な(const char *)(...)キャストとして定義する傾向があります。ボードがそれを必要としない場合、実際の効果はありませんが、コードを必要とするボードに移植すると大幅に節約されます。
マジェンコ

5

Makenkoの優れた答えを詳しく説明するために、コンパイラがこれについて警告する正当な理由があります。テストスケッチを作成しましょう。

char *foo = "This is some text";
char *bar = "This is some text";

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  foo [2] = 'o';     // change foo only
  Serial.println (foo);
  Serial.println (bar);
  }  // end of setup

void loop ()
  {
  }  // end of loop

ここには、fooとbarという2つの変数があります。setup()でそれらの1つを変更しますが、結果は次のとおりです。

Thos is some text
Thos is some text

彼らは両方とも変わった!

実際、警告を見ると、次のことがわかります。

sketch_jul14b.ino:1: warning: deprecated conversion from string constant to char*’
sketch_jul14b.ino:2: warning: deprecated conversion from string constant to char*’

コンパイラはこれが危険であると知っており、それは正しいです!この理由は、コンパイラーが(合理的に)文字列定数が変わらないことを期待しているからです(定数であるため)。したがって"This is some text"、コード内で文字列定数を複数回参照する場合、それらすべてに同じメモリを割り当てることができます。1つを変更すると、それらすべてを変更することになります。


聖煙!誰が知っていただろうか?これは最新のArduinoIDEコンパイラにも当てはまりますか?ESP32で試してみたところ、GuruMeditationエラーが繰り返し発生します。
-not2qubit

@ not2qubit Arduino 1.8.9でテストしたところ、それは事実です。
ニックギャモン

警告には理由があります。今回は次のようになりました:警告:ISO C ++は、文字列定数を 'char 'に変換することを禁止しています [-Wwrite-strings] char bar = "This is some text"; -FORBIDSは強力な言葉です。あなたはそうすることを禁じられているので、コンパイラは自由にいじって、2つの変数で同じ文字列を共有できます。禁じられた事をしないでください!(また、警告を読んで削除します)。:)
ニックギャモン

したがって、このような安っぽいコードに出くわし、その日を乗り切りたい場合に備えてください。異なる文字列"constants"の初期宣言*foo*bar使用は、これが起こらないようにしますか?また、これは、次のような文字列をまったく配置しないこととどのように異なりますか?char *foo;
not2qubit

1
別の定数は役立つかもしれませんが、個人的に私は入れないだろう何かをそこに、後で通常の方法でそこにデータを入れて(例えば。でnewstrcpydelete)。
ニックギャモン


3

例:

void foo (char * s)
  {
  Serial.println (s);
  }

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  foo ("bar");
  }  // end of setup

void loop ()
  {
  }  // end of loop

警告:

sketch_jul14b.ino: In function ‘void setup()’:
sketch_jul14b.ino:10: warning: deprecated conversion from string constant to ‘char*’

この関数fooはchar *を予期します(したがって変更できます)が、変更しない文字列リテラルを渡します。

コンパイラはこれをしないよう警告しています。非推奨であるため、将来のコンパイラバージョンでは警告からエラーに変わる可能性があります。


解決策:fooがconst charを取るようにする*:

void foo (const char * s)
  {
  Serial.println (s);
  }

わかりません。変更できないという意味ですか?

古いバージョンのC(およびC ++)では、上記の私の例のようなコードを記述できます。あなたは、関数(のように作ることができfoo、あなたがそれまで渡す何かを印刷し)、その後、文字列リテラル伝承(例えば。foo ("Hi there!");

ただし、char *引数として受け取る関数は、その引数を変更できます(つまりHi there!、この場合は変更できます)。

たとえば、次のように書いたかもしれません。

void foo (char * s)
  {
  Serial.println (s);
  strcpy (s, "Goodbye");
  }

残念ながら、リテラルを渡すことで、そのリテラルを潜在的に変更して、「こんにちは!」今は「さようなら」で、これは良くありません。実際、長い文字列でコピーした場合、他の変数を上書きする可能性があります。または、一部の実装では、「こんにちは」という理由でアクセス違反が発生します。読み取り専用(保護された)RAMに置かれた可能性があります。

そのため、コンパイラライターはこの使用法を徐々に非推奨にしているため、リテラルを渡す関数はその引数をとして宣言する必要がありますconst


ポインターを使用しない場合、問題がありますか?
フェデリココラッツァ

どんな問題?この特定の警告は、文字列定数をchar *ポインターに変換することに関するものです。詳しく説明してもらえますか?
ニックギャモン

@Nick:「(..)文字列リテラルを渡していますが、変更しないでください」とはどういう意味ですか。わかりません。can not変更されるということですか?
マッドスキャーン16年

答えを修正しました。Majenkoは彼の答えでこれらのポイントのほとんどをカバーしました。
ニックギャモン

1

私はこのコンパイルエラーがあります:

TimeSerial.ino:68:29: warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]
   if(Serial.find(TIME_HEADER)) {

                         ^

この行を置き換えてください:
#define TIME_HEADER "T" // Header tag for serial time sync message

この行で:
#define TIME_HEADER 'T' // Header tag for serial time sync message

コンパイルはうまくいきます。


3
この変更は、文字の資本T.のためのASCIIコードの値を持つ単一の文字に1つの文字列「T」から定義変更
DLU
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.