C言語関係掲示板

過去ログ

No882 constの書き換え BCC5.5のバグ?

[戻る] [ホームページ]
No.11408

constの書き換え
投稿者---ひよこ(2003/12/22 19:51:33)


こんばんは、ひよこです。
初心者です。
また、バカな質問だと思いますが、よろしくお願いします。

#include <stdio.h>

void ary_cpy(int a[],const int b[],int no) //aにbをコピーする
{
    while(no-->0)
        *a++ = *b++;
}

int main(void)
{
    int y[5]={1,2,3,4,10},x[5];
    int i;
    int a_size=sizeof(y)/sizeof(y[0]);

    ary_cpy(x,y,a_size);

    for(i=0;i<a_size;i++)
        printf("x[%d]= %d\n",i,x[i]);

    return 0;
}



これをコンパイルすると”constオブジェクトは変更できない”と
なるのですがどうしてでしょうか?

void ary_cpy(int a[],const int b[],int no)
{
    int i;

    for(i=0;i<no;i++)
        a[i]=b[i];
}


や、
void ary_cpy(int a[],const int b[],int no)
{
    int i;

    for(i=0;i<no;i++)
        *(a+i)=*(b+i);
}


などはできるのですが、最初のとなにが違うのか分かりません。
コンパイラはBCC5.5です。
よろしくお願いします。


No.11410

Re:constの書き換え
投稿者---YuO(2003/12/22 20:09:39)


>これをコンパイルすると”constオブジェクトは変更できない”と
>なるのですがどうしてでしょうか?
>コンパイラはBCC5.5です。

コンパイラのバグだと思います。
#意味解釈の所に問題がありそう……。

void ary_cpy(int * a,const int * b,int no) /*aにbをコピーする*/

のように書き換えれば,ちゃんとコンパイルを通ります。


No.11413

Re:constの書き換え
投稿者---おでん(2003/12/22 20:32:09)


>>これをコンパイルすると”constオブジェクトは変更できない”と
>>なるのですがどうしてでしょうか?
>>コンパイラはBCC5.5です。
>
>コンパイラのバグだと思います。
>#意味解釈の所に問題がありそう……。
>
>
void ary_cpy(int * a,const int * b,int no) /*aにbをコピーする*/

>のように書き換えれば,ちゃんとコンパイルを通ります。


配列名は定数です。
ポインタは変数です。
・・・ポインタと配列はまったく違いますよ (^.^)

No.11415

Re:constの書き換え
投稿者---NykR(2003/12/22 20:46:58)


>配列名は定数です。
>ポインタは変数です。
>・・・ポインタと配列はまったく違いますよ (^.^)

そうですね。
#ポインタが変数かどうかはともかく

void ary_cpy(int a[],const int b[],int no) //aにbをコピーする

と書こうが

void ary_cpy(int * a,const int * b,int no) /*aにbをコピーする*/

と書こうが、aもbもポインタであって配列ではありません。
同じことを書いているのに、コンパイルを通ったり通らなかったりするのだから、
おかしいのはプログラムではなく、コンパイラだということになるのではないでしょうか。

No.11416

Re:constの書き換え
投稿者---たか(2003/12/22 21:01:08)


Borland-C++5.6.4ではどれもエラー無しで通ります。

BCC5.5は重大なバグがあったそうなので5.5.1で試してみられてはいかが
でしょうか。


No.11422

Re:constの書き換え
投稿者---YuO(2003/12/22 23:16:52)


>Borland-C++5.6.4ではどれもエラー無しで通ります。
>BCC5.5は重大なバグがあったそうなので5.5.1で試してみられてはいかが
>でしょうか。

5.5.1でもだめでした。
#というか,私の手元には5.5.1しかない。

C++Builder Xを落とそうかなぁ……。
#せっかくの日本語版(5.5.1)なので,乗り換えを躊躇っている。


No.11417

Re:constの書き換え
投稿者---おでん(2003/12/22 21:44:55)


>>配列名は定数です。
>>ポインタは変数です。
>>・・・ポインタと配列はまったく違いますよ (^.^)
>
>そうですね。
>#ポインタが変数かどうかはともかく
>
> void ary_cpy(int a[],const int b[],int no) //aにbをコピーする
>
>と書こうが
>
> void ary_cpy(int * a,const int * b,int no) /*aにbをコピーする*/
>
>と書こうが、aもbもポインタであって配列ではありません。
>同じことを書いているのに、コンパイルを通ったり通らなかったりするのだから、
>おかしいのはプログラムではなく、コンパイラだということになるのではないでしょうか。

例えば、
a[10];
b[10];
と言う宣言があってa=bは出来ませんよね?・・・それが「変数ではない」と言う根拠です。
変数でないのだからインクリメント/デクリメントは当然出来ません。
まして、受け取った関数の方で「配列だ」て宣言している
==「ポインタとしては使わない」といってるわけですから・・・

a[]のときの'a'は配列の名前であって、ポインタの名前ではありません。
名前を変える事は出来ないですね?
#a[]をa++とやるとbになるのでしょうか?

その辺は、C-FAQの6章あたりから始まる議論を参照してみてください。
http://www.catnet.ne.jp/kouno/c_faq/c_faq.html ←日本語訳

No.11424

Re:constの書き換え
投稿者---YuO(2003/12/22 23:36:24)


せっかくなので……。


>例えば、
>a[10];
>b[10];
>と言う宣言があってa=bは出来ませんよね?・・・それが「変数ではない」と言う根拠です。

根拠になりません。
a = b;
としたときに,aに要求されるのは,変更可能な左辺値であることです。

例えば,ポインタがオブジェクトを指し示しているとき,
単項*演算子は左辺値を作り出しますから,pがint *型で正しくint型のオブジェクトを指しているとき,
*p = 10;
ということができます。
#*pは変更可能な左辺値になるから。

逆に,
extern const int a;
int b;
とあった時に,
a = b;
はaが左辺値であっても変更可能でないので制約違反です。


ちなみに,左辺値と変更可能な左辺値についてJIS X 3010-1993の規定をまとめると,
左辺値
オブジェクトを指し示す式。
変更可能な左辺値
以下を満たす左辺値。
  • 配列型をもたない
  • 不完全型を持たない
  • const修飾型をもたない
  • 構造体又は共用体の場合,const修飾型のメンバをもたない(再帰適用)

となります。


>変数でないのだからインクリメント/デクリメントは当然出来ません。

増分・減分演算子は,スカラ型の変更可能な左辺値をオペランドとして要求します。
そして,増分・減分演算子に関してC++でいうArray-to-pointer conversionが適用されませんから,
配列に対して適用することは出来ません。


>まして、受け取った関数の方で「配列だ」て宣言している
>==「ポインタとしては使わない」といってるわけですから・・・

これに関しては,No. 11420に書いたとおり。
関数仮引数中での配列型はポインタ型に読み替えるのが言語規則です。


>その辺は、C-FAQの6章あたりから始まる議論を参照してみてください。
>http://www.catnet.ne.jp/kouno/c_faq/c_faq.html ←日本語訳

http://www.catnet.ne.jp/kouno/c_faq/c6.html#3
http://www.catnet.ne.jp/kouno/c_faq/c6.html#4
をちゃんと読みましたか?

No.11418

Re:constの書き換え
投稿者---かずま(2003/12/22 21:53:31)


> 配列名は定数です。

定数ではありません。次のプログラムを実行してみてください。

#include <stdio.h>
void f(void) { int a[3]; printf("a=%p\n", a); }
void g(void) { f(); }
int main(void) { f(); g(); return 0; }

配列は、sizeof演算子と単項&演算子のオペランドとなるとき以外は、
常に、先頭要素へのポインタの値に変換されるという規則があります。


> ポインタは変数です。

K&R2 にも最初はそう書いてあり、そう思い込んでいる人が多いようですが、
実際には、「ポインタは型です。」のほうが正しい解釈です。

「int は変数です。」と言われたら反論したくなりませんか。
int は型ですから、変数もあれば、定数もある。式の値でもあるわけです。
int i; のとき、変数 i は int。定数 3 は int。式(i + 3) の値も int です。

int *p = &i; のとき、確かに変数 p はポインタですが、
&i もポインタです。しかし、&i は変数ではなく、式です。


No.11419

Re:constの書き換え
投稿者---おでん(2003/12/22 21:57:58)


><pre>
> 配列名は定数です。

定数ではありません。次のプログラムを実行してみてください。

#include <stdio.h>
void f(void) { int a[3]; printf("a=%p\n", a); }
void g(void) { f(); }
int main(void) { f(); g(); return 0; }

配列は、sizeof演算子と単項&演算子のオペランドとなるとき以外は、
常に、先頭要素へのポインタの値に変換されるという規則があります。


> ポインタは変数です。

K&R2 にも最初はそう書いてあり、そう思い込んでいる人が多いようですが、
実際には、「ポインタは型です。」のほうが正しい解釈です。

「int は変数です。」と言われたら反論したくなりませんか。
int は型ですから、変数もあれば、定数もある。式の値でもあるわけです。
int i; のとき、変数 i は int。定数 3 は int。式(i + 3) の値も int です。

int *p = &i; のとき、確かに変数 p はポインタですが、
&i もポインタです。しかし、&i は変数ではなく、式です。
</pre>

ご指摘いただいたこと、納得いたしました。また、
「配列は、sizeof演算子と単項&演算子のオペランドとなるとき以外は、
常に、先頭要素へのポインタの値に変換されるという規則があります。」
と言う事から変更できない(定数のように振舞う・・・でしょうか?)
・・・ということですね?・・・間違っているようでしたらご指摘ください。

No.11426

ありがとうございます。
投稿者---ひよこ(2003/12/22 23:51:32)


YuOさん、おでんさん、NykRさん、たかさん、かずまさん、
ありがとうございます。

皆さんの教えてくださった事を、自分なりに解釈した結果、
.廛蹈哀薀爐牢岼磴辰討い覆
▲灰鵐僖ぅ蕕離丱
だが、
BCCの他のコンパイラなら通る
が、
BCC5.5では通らない
と、解釈しました。

void ary_cpy(int *a,const int *b,int no)
の方も試してみると直ぐにコンパイルされました。

いろいろとこぼれ話などもありまして非常に参考に
なりました。

また、なにかありましたらよろしくお願い致します。

No.11420

Re:constの書き換え
投稿者---YuO(2003/12/22 23:11:53)


根本的なところを間違っているようなので……。


void ary_cpy(int * a,const int * b,int no) /*aにbをコピーする*/
のように書き換えれば,ちゃんとコンパイルを通ります。

配列名は定数です。
ポインタは変数です。
・・・ポインタと配列はまったく違いますよ (^.^)



配列型のオブジェクトが変更不可能な左辺値であることは確かです。
#配列型のオブジェクトは定数ではありません。
しかし,ここで問題にしているのは,関数の仮引数中にある配列型の問題です。

関数の仮引数にある配列型及び関数型の引数は,JIS X 3010-1993 6.7.1 関数定義の意味規則の第三段落や,
ISO/IEC 9899:1999 6.7.5.3 Function declarators (including prototypes)のParagraph 11及び12によって,
配列型や関数型ではなく「配列の要素へのポインタ」型や「関数へのポインタ」型の引数として扱われます。
その結果引数はオブジェクトになり,オブジェクト自体はconst修飾されておらず配列型でもないので,
常に変更可能な左辺値になり得ます。


ちなみに,式中に出てくる配列指示子や関数指示子がポインタに変換される場合,
それらは既に左辺値ではありません。


このあたりはややこしいので,ちゃんと勉強して下さい。


No.11425

Re:constの書き換え
投稿者---おでん(2003/12/22 23:42:55)


>根本的なところを間違っているようなので……。
>
>
>
void ary_cpy(int * a,const int * b,int no) /*aにbをコピーする*/
のように書き換えれば,ちゃんとコンパイルを通ります。

配列名は定数です。
ポインタは変数です。
・・・ポインタと配列はまったく違いますよ (^.^)


>
>配列型のオブジェクトが変更不可能な左辺値であることは確かです。
>#配列型のオブジェクトは定数ではありません。
>しかし,ここで問題にしているのは,関数の仮引数中にある配列型の問題です。
>
>関数の仮引数にある配列型及び関数型の引数は,JIS X 3010-1993 6.7.1 関数定義の意味規則の第三段落や,
>ISO/IEC 9899:1999 6.7.5.3 Function declarators (including prototypes)のParagraph 11及び12によって,
>配列型や関数型ではなく「配列の要素へのポインタ」型や「関数へのポインタ」型の引数として扱われます。
>その結果引数はオブジェクトになり,オブジェクト自体はconst修飾されておらず配列型でもないので,
>常に変更可能な左辺値になり得ます。
>
>
>ちなみに,式中に出てくる配列指示子や関数指示子がポインタに変換される場合,
>それらは既に左辺値ではありません。
>
>
>このあたりはややこしいので,ちゃんと勉強して下さい。

以下の考えは間違っているのでしょうか?

void aa(){
  int a[10];
  int *p= a;

  abc(a,p);

}

void abc(int a[],int *p){
ここのスコープでは、aは配列として宣言されている(実際はアドレスです)
従って変更不可
(これを、*aとして受ければ変更できます。が、意味が違うと思います)
pは同じところを指しているが、ポインタなので変更可
}



No.11427

Re:constの書き換え
投稿者---YuO(2003/12/22 23:55:04)


>以下の考えは間違っているのでしょうか?
void abc(int a[],int *p){
ここのスコープでは、aは配列として宣言されている(実際はアドレスです)
従って変更不可
(これを、*aとして受ければ変更できます。が、意味が違うと思います)
pは同じところを指しているが、ポインタなので変更可
}


間違っています。
関数abc内において,aはint *型(つまりポインタ型)を持つオブジェクトです。
決して,int型の配列型を持つオブジェクトではありません。

故に,aは変更可能な左辺値として機能するので,aの持つ値を変更することは可能です。
例えば,
void abc(int a[], int *p) {
    int n = 0;
    a = &n;
}

は正しいCプログラムです。
#外部から見ると副作用が存在しないので呼び出す意味はないですが。


さらに言うと,
> (実際はアドレスです)
は意味不明です。
ポインタとはオブジェクト,不完全型または関数を参照するための値であって,
ポインタがアドレスである必要性はありません。
#オブジェクトの番号とオフセットでも構わない。


No.11428

Re:constの書き換え
投稿者---おでん(2003/12/23 00:01:28)


>ポインタとはオブジェクト,不完全型または関数を参照するための値であって,
>ポインタがアドレスである必要性はありません。
>#オブジェクトの番号とオフセットでも構わない。

上記お話は、理解できました。
アドレスである必要はありません。

No.11441

Re:constの書き換え
投稿者---YuO(2003/12/24 20:39:18)


>上記お話は、理解できました。

もう一方は,「何が」理解できないのですか?

関数の仮引数中の配列型のオブジェクトの宣言は,
その要素型へのポインタ型のオブジェクトの宣言に置き換えられる,
ということは言語規約で定められていることですから,
「そういうものだ」と納得するしかない事項です。