質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

3回答

4331閲覧

修飾子が付くと違う型になるのでしょうか?

sk28sk

総合スコア10

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

1クリップ

投稿2016/06/16 15:07

###前提・実現したいこと
大学の授業で出された課題で、ポインタを使って数字を入力すると英語になって表示するプログラムを作りました。
最初にmain内にてchar *aを作って実行したところ下記のエラーメッセージが出たため、const char *aにしたところ上手くいきました。が、なぜ上手くいったのかよくわかっておりません。
const char *const month[]を使った理由も、先生から使うよう指示が出ていたためで、なぜそれを使うのかも分かっておりません。
ポインタは始めたばかりで int p のような使い方しかしてこなかったため、なぜ急に修飾子であるconts にをつけたのかもイマイチです・・・
質問ばかりで申し訳ございません。どうかよろしくお願いいたします。

###発生している問題・エラーメッセージ
17:4: warning: assigning to 'char *' from 'const char *' discards
qualifiers [-Wincompatible-pointer-types-discards-qualifiers]
a=month_name(k);

###該当のソースコード
#include <stdio.h>

const char *const month_name(int n){
static const char *const months[] = { "January","february","march","april","may","june","july","august","september","october","november","december","unknown month"};

if(0<n && n<12){
return months[n-1];
}else{
return months[12];}

}

int main(){
char *a;
int k;
scanf("%d",&k);
a=month_name(k);
printf("%s\n",a);
return 0;
}

###試したこと
main関数の
char *a

const char *a;
に変えました。

###補足情報(言語/FW/ツール等のバージョンなど)
C言語

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

Cの型システムではchar *const char *const char *constは別の型として区別されます。今回の動作を厳密に理解するには、constとは何か、lvalue(左辺値)とrvalue(右辺値)の違いを知る必要があります。また、C++だと少し異なるのでそれを交えて説明していきます。

constは「変更不可」を表す修飾子です。constが付いた型の部分へ代入等による破壊的な変更を行おうとした場合、コンパイラはエラーとし、変更されないことを保証します。また、変更されないことを前提にコンパイラは最適化を行い、より効率的に処理ができるようになります。

では、何が「変更不可」となるのかを見ましょう。constconst 型または型 constと型にかかる場合と、*constとポインタにかかる場合があります。型にかかる場合はその型であるデータの部分が「変更不可」になり、ポインタの場合はポインタそのものが「変更不可」になります。

例として、const char *const aを見ましょう。aというconst charへのポインタの値を変更できません。また、*aとaが示す先のデータの値も同じく変更できません。コレとは違い、const char *bとした場合は、bというポインタ自体は変更できますが、*bとbが示す先は変更できません。char *cはどれもこれも変更可能になります。char *const dは少し奇妙で、d自体のポインタ値は変更できませんが、*bという示す先のデータの値は変更できます。

C

1const char *const a = "abc"; 2const char *b = "def"; 3char *c = "ghi"; 4char *const d = "jkl"; 5a = "hoge"; // エラー 6*a = 'A'; // エラー 7b = "hoge"; // OK 8*b = 'A'; // エラー 9c = "hoge"; // OK 10*c = 'A'; // OK だが、別の理由で動作は未定義 11d = "hoge"; // エラー 12*d = 'A'; // OK だが、別の理由で動作は未定義

さて、最初に区別すると言いましたが、完全に別扱いでは不便です。そこで、constには暗黙の型変換(キャスト)というのがあります。制限が緩い方(constがない)からきつい方(constがある)、つまり、constを追加する場合は暗黙に型変換してくれます。

C

1char *a = "abc"; 2const char *b = "def"; 3b = a; // OK 4a = b; // 警告 5a = (char *)b; // 明示的にキャストすればOK

逆ができないのはintlong longで大きい方にはできるが小さい方にはできないのと同じです。明示的にキャストすればコンパイルが通るのも同じです。(C++では専用のconst_castが用意されています)

ということで、char *const char *を入れようとしても警告(C++ではより厳密になるためエラー)になります。なので、const char *にするのです。あれ、でも、関数の戻りの型はconst char *constでしたよね?constが一つ多いし、const char *には入らないのでは思うことでしょう。いや、思ってください。

今から説明するのは、一度でうまく理解できるのは難しいと思います。私もうまく説明できるかは自信がありません。ただ、Cをよく理解し、C++へ繋げて行くには必要な知識だと思っています。

まず、lvalue(左辺値)とrvalue(右辺値)の説明をします。代入演算子(=)の左辺に来る式の結果の値をlvalue、右辺に来る式の結果の値をrvalueと呼びます。左と右、単純ですね。ここで重要なのは、constはlvalueにだけしか適用されず、rvalueでは「変更不可」の意味を失うと言うことです。つまり、rvalue自体にかかっているconstは失われると言うルールです。

C

1const int x = 0; 2int y = 1; 3x = y; // エラー 4y = x; // OK

x = yがエラーになる理由は単純です。xは「変更不可」にしたので代入による変更はできません。では、y = xはどうでしょう。yint型、xconst int型と型が違っています。constを足す方への暗黙の型変換は問題ないけど、constを外す方は明示しないと無理だと行ったはずなのに、警告もエラーもありません。その理由は何か?それは、yはrvalueなのでconstが失っているからです。

考えてみてください。代入は値のコピーです。xの値はコピー元ですので、コピー先のyがこの後どのような値に変わっても、xの値そのものが変更されることは決してありません。ですので、rvalueのときは、値そのものに対するconstを保持する必要がなくなるのです。

おおっと、ここでもう一つ注意点があります。あくまで失われるのは値そのものに対するconstだけです。const char *const a;と言う形だった場合はaそのものにかかるconstだけが外れてconst char *型になります。これはコピー元のaは示す先のデータは、コピー先をゴニョゴニョすることで書き換えることができるからです。示す先を「変更不可」として守るために、その部分に対する「変更不可」であるという情報は残しておかなくてはなりません。

これで、問題にあった関数の戻りの型も理解できましたね。const char *constという戻り値の型でしたが、rvalueですのでconst char *になったと言うことです。なので、同じ型であるconst char *aには警告もエラーも無く代入できるようなったと言うのがこのコードの動きになります。

※ 気付いたと思いますが、関数はconst char *month_name(int n)としても問題ありません。returnで指定しているmonths[n - 1]const char *const型ですが、これもrvalueであるため、この時点でconst char *になっています。二番目のconstCでは全く意味が無いことになります。(C++は異なります)

※ C++だともう少し複雑です。C++では、lvalueとrvalueの定義が異なっており、また、rvalue reference(右辺値参照)というのがあり、関数の戻り値でもconst付きは意味がある物に変わります。詳しくはconst rvalue referenceは何に使えばいいのか - ここは匣を見てください。すいませんが、私もよくわかっていません。

参考文献: const type qualifier - cppreference.com

投稿2016/06/17 12:07

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

こんにちは。

constは本当に難しいです。
まず、C言語の文法上const char* constchar const * constと書いても同じ意味となります。
そして、constや*等の修飾は、charに近い方から解釈されます。

つまり、char constchar型(修正不可)です。
char const *「char型(修正不可)」へのポインタです。
そして、char const * constは、「char型(修正不可)」へのポインタ(修正不可)となります。

さて、実はmonth_name()関数の戻り型をchar const *としてもコンパイル・エラーはでません。
理由は非常に難しく、私の説明力では確実に更なる混乱を招きますので省略させて下さい。
以後の説明はconst char * month_name(int n);と定義されているとします。

char *a; ... a=month_name(k);とした時、警告が表示される理由は以下です。
char *a;は、char型へのポインタです。修正不可がついていないため、そのchar型の値を変更できます。例えば、a=month_name(1);とするとaには"January"の先頭アドレスが設定されます。
そして、aの指す先が変更可能なので、例えば*a='K';とできてしまいます。これを本当にやってしまうと"Kanuary"となりますね。以後、month_name(1);は"Kanuary"を返却するようになるのです。困ったことです。

最近のOSならば定数エリアを変更しようとするとプログラムが落ちますので早期にバグ検出できますが、処理系によってはエラーが発生せずに本当に"Kanuary"となってしまいます。これは非常に見つけにくいバグです。
そのようなバグを防ぐために、char const* a;と書きます。こうすればaは修正不可ですのでa='k';はコンパイル・エラーになり、早期にバグ検出できると言う寸法なのです。

その早期のバグ検出機能を有効に機能させるため、char const*型の値をchar *型変数へ設定しようとすると、本当に大丈夫ですか?と警告してくれているのです。

投稿2016/06/16 15:56

編集2016/06/16 15:59
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

なぜ上手くいったのかよくわかっておりません。

const char *const month_name は以下のような制約付きとなります
・変数として保持しているアドレスを書き換えないでね(変数への制約)
・変数として保持しているアドレスが指し示す先にあるオブジェクトを書き換えないでね(参照先への制約)

ところがこの返却値をchar *a で受け取ってしまうと
上記の制約が効かなくなってしまうのです

言い換えると、month_nameは自身の書き換えや参照先の書き換えができないのですが
aはいずれもできてしまうのです、constがつけられていないから
こうなっては制約の抜け道が出来てしまうので、
a=month_nameの代入をエラーにします

おまけ

nは12でもよいのではないですか?
12月がunknown monthになっちゃいませんか?

投稿2016/06/16 15:44

777

総合スコア34

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問