配列とポインタが分からない

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
かも

配列とポインタが分からない

#1

投稿記事 by かも » 7年前

大学の講義でプログラミングの勉強を行っているのですが配列とポインタでつまづいてます。

N個の要素を持つ整数型配列aに適当なデータを初期化で代入し、その後,そのデータを、配列bには順方向、配列cには逆方向に、ポインタx,y,zを用いて代入し、最後に配列a,b,cの要素を出力せよ。また、代入にはインクリメントとデクリメントを繰り返す方法を用いることと、代入と出力は別々のfor文を用いるという条件があります。

自分が作ったプログラムだとエラーはでないのですが数値がおかしくなります。
どこが間違っているのかご指摘をよろしくお願いします。

コード:

#include<stdio.h>
#define N 10
int main(void) {
int a[N]={1,2,3,4,5,6,7,8,9,10},b[N],c[N];
int *x,*y,*z,i;
x=a;
y=b;
z=c+N-1;
for(i=0;i<N;i++) {
*y=*x;
*z=*x;
x++;
y++;
z--;
}
for(i=0;i<N;i++) {
printf("a[%d]=%d b[%d]=%d c[%d]=%d \n",i,*x,i,*y,i,*z);
}
return(0);
}

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: 配列とポインタが分からない

#2

投稿記事 by みけCAT » 7年前

  • 14行目で、ポインタの演算で許される範囲の外にポインタを移動させている
  • 17行目で、最後の要素の1個次を指しているポインタxおよびyをデリファレンスしている
ため、未定義動作になります。
(デリファレンスしなければ、配列の最後の要素の1個次を指すポインタを作ることは問題ありません)

通常はこのz--;による範囲外のポインタの作成が問題になることはないでしょうが、
17行目で配列の要素を読み取らず適当な値をprintfに渡すのはよくないですね。
きちんとポインタの初期化とインクリメントを行うようにするか、
かも さんが書きました:また、代入にはインクリメントとデクリメントを繰り返す方法を用いることと、代入と出力は別々のfor文を用いるという条件があります。
という条件なので、出力には素直に添字演算子を使うといいでしょう。

代入において、最後のループではデクリメントをしないことで未定義動作を回避するとさらにいいでしょう。

さらに、インデントを整えるとより良くなるでしょう。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

かも

Re: 配列とポインタが分からない

#3

投稿記事 by かも » 7年前

出力に添字演算子を用いることで正常な数値をだせることができました。
本当にありがとうございます。
しかし、自分はまだプログラミングが全然得意ではなく、みけCATさんが最初に指摘してくれた14行目と17行目の2つの問題点が、どういうことなのか言ってくれていることがなんとなく理解できるんですが、いざそれを修正しようと思うとどの部分をどのように修正するべきなのか全くわかりません。
もしよろしければこの2つの点についての説明をお願いします。

コード:

#include<stdio.h>
#define N 10
int main(void) {
int a[N]={1,2,3,4,5,6,7,8,9,10},b[N],c[N];
int *x,*y,*z,i;
x=a;
y=b;
z=c+N-1;
for(i=0;i<N;i++) {
*y=*x;
*z=*x;
z--;
x++;
y++;
}
for(i=0;i<N;i++) {
printf("a[%d]=%d b[%d]=%d c[%d]=%d \n",i,a[i],i,b[i],i,c[i]);
}
return(0);
}

たいちう
記事: 418
登録日時: 13年前

Re: 配列とポインタが分からない

#4

投稿記事 by たいちう » 7年前

正確には14行目ではなく、12~14行目ですね。
例えばxについては、最初に配列の先頭を指していたポインタは、
10回目のインクリメントをすることで、4行目で確保された配列aの範囲から飛び出します。
飛び出すだけなら良いのですが、17行目でそのポインタを使っているのがまずいのです。

今回の問題は、
1) 配列を初期化する
2) ポインタを使って配列をコピーする
3) コピーされた配列を確認する
という意味でしょうから、17行目の確認でポインタを使う必要はなく、
コピーの最後でポインタが範囲外を指すことになっても構わないでしょう。


> 代入において、最後のループではデクリメントをしないことで未定義動作を回避するとさらにいいでしょう。

と、みけCATさんは書かれていて、もし対応するならば、インクリメント等をifで囲みます。

コード:

if (i < N - 1) {
	x++;
	y++;
	z--;
}
私ならば気にしないことなので、No.3のプログラムで及第点です。
ただしインデントは気にしますので、私のコードとの違いを考えてみてください。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: 配列とポインタが分からない

#5

投稿記事 by みけCAT » 7年前

たいちう さんが書きました:正確には14行目ではなく、12~14行目ですね。
繰り返しになりますが、C言語では「配列の最後の要素の1個次」を指すポインタを作ることは明示的に認められています。
したがって、12行目と13行目のインクリメントは、それだけでは問題になりません。
しかし、デクリメントにより「配列の最初の要素の1個前」に行くのは、許されるという記述がありません。
よって、14行目は引き算の結果がもとのポインタと同じ配列の要素でもその1個後でもなくなるので、未定義動作になります。
(No: 3のコードではデクリメントの位置が変わっていますが、ここの行数はNo: 1のコードのものです)

N1570 6.5.6 Additive operatorsの8より引用
Moreover, if the expression P points to the last
element of an array object, the expression (P)+1 points one past the last element of the
array object, and if the expression Q points one past the last element of an array object,
the expression (Q)-1 points to the last element of the array object. If both the pointer
operand and the result point to elements of the same array object, or one past the last
element of the array object, the evaluation shall not produce an overflow; otherwise, the
behavior is undefined. If the result points one past the last element of the array object, it
shall not be used as the operand of a unary * operator that is evaluated.
たいちう さんが書きました:例えばxについては、最初に配列の先頭を指していたポインタは、
10回目のインクリメントをすることで、4行目で確保された配列aの範囲から飛び出します。
飛び出すだけなら良いのですが、17行目でそのポインタを使っているのがまずいのです。
「(1個だけ後に)飛び出すだけならいいが、それを使う(デリファレンスする)のがまずい」ということで正しいです。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

たいちう
記事: 418
登録日時: 13年前

Re: 配列とポインタが分からない

#6

投稿記事 by たいちう » 7年前

みけCATさん、解説ありがとうございます。
ようやく趣旨が理解できたと思います。

No.1のプログラムの12-13行目は問題ないので、
私のNo.4のif分の中もデクリメントのみですね。

かずま

Re: 配列とポインタが分からない

#7

投稿記事 by かずま » 7年前

こう書けば良いのでは?

コード:

#include <stdio.h>

#define N 10

int main(void) 
{
    int a[N] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, b[N], c[N];
    int *x, *y, *z, i;
    x = a;
    y = b;
    z = c + N;  // ポインタは配列の最後の要素の次を指せる
    for (i = 0; i < N; i++) {
        z--;    // z が配列 c の範囲以外の要素を指すことはない
        *y = *x;
        *z = *x;
        x++;
        y++;
    }
    x = a;  // 出力の前に再初期化
    y = b;
    z = c;
    for (i = 0; i < N; i++) {
        printf("a[%d]=%d b[%d]=%d c[%d]=%d \n", i, *x, i, *y, i, *z);
        x++;  // 次の要素を指す
        y++;
        z++;
    }
    return (0);
}

かも

Re: 配列とポインタが分からない

#8

投稿記事 by かも » 7年前

みけCATさん、たいちうさん、かずまさん解説ありがとうございます。
14行目のデクリメントと17行目がどうしていけなにのかをようやく理解することができました。

fm

Re: 配列とポインタが分からない

#9

投稿記事 by fm » 7年前

本の紹介になってしまいますが、余計なお節介だったらすみません。

C言語 ポインタ 書籍と検索してみたら、良い本がありました。自分はアマゾンのお気に入りに入れましたが

前橋 和弥さんの「C言語ポインタ完全制覇」か、 柴田 望洋さんの「詳解C言語 ポインタ完全攻略」
がおススメです。

あとは、有名どころではK&Rくらいでしょうか。(自分は持っていないのでモグリですし、にわかですが)

閉鎖

“C言語何でも質問掲示板” へ戻る