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

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

ただいまの
回答率

90.35%

  • C

    3956questions

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

  • C++

    3737questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

char (*p)[10]の使い道

受付中

回答 9

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,419

strike1217

score 563

char (*p)[10];
>>>char型の要素を10個持つ配列へのポインタ!
これの使い道がわかりません。

int (*arp)[10];

// アドレスを入れるときは以下のように入れます。

int array_ten[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
arp = &array_ten;

普通に、int *arp = array_ten; で良いような気がします。
わざわざ要素数が固定のポインタに渡す必要性が見えません。

「あ!ポインタのポインタの初期化に使えるかな?」と思い、以下のようにしてみました。

char buf[3][100] = {
        "nononono!!",
        "I love you!",
        "Thanks"
    };

    char **p;
    // char *q[3] = {buf[0], buf[1], buf[2]}; こっちは成功
    char (*q)[100] = buf;
    p = &q;


これをやると、warningが出てきて、さらに落ちます・・・・
なぜかわかりませんが・・・

char (*p)[10];
関数に2次元配列を渡す時と、列を動的確保したい時ぐらいしか、出番がないような気がするんですが・・・・

char (*p)[10];
この子の他の使い方はあります??
プログラムの例を見ているとよく出てきます。

printf("p[1] : %p, p[1][0] : %p\n", p[1], &p[1][0]);
おまけに2次元配列のようにアクセスもできています・・・・!?
正直、よくわかってないです。

どなたか教えてください!

[追記]

char array[4][10]
char (*arp)[10] = array;

たとえば、こうなった場合・・・
arpという1つのポインタ4つの配列の先頭アドレスを指しているということですよね?
・・・・??
なんかおかしくないですかね?
たった1つしかないポインタが同時に4つのアドレスを指しているなんて・・・
どういうことですか??

[追記2]

ポインタのポインタの初期化について、考察してもらっている方もいらっしゃるのですが、
char array[4][10]
char (*arp)[10] = array; ← arpはポインタで要素数4つの配列の先頭アドレスを持っていると思います。

つまり・・・・・一次元配列・・・ということになりますよね??
arp[0], arp[1] ・・というのが、要素数10個ある配列の先頭を表すことで、あたかも2次元配列のようになる
と解釈しました。

しかし、

char ii[4] = "GHYU";
char *qq = ii;
char **pp = &qq;   // char *pp = &qq; これは無理!


これは、問題がありません。
ポインタのポインタを初期化できています。

char (*arp)[10]; はこれに似ていと思います。
あたかも「char ii[4] = "GHYU";」+「char *qq = ii;」これを1つにしたような感じです。

G, H, Y, U が要素数10個配列の先頭の文字だと想像してください。

なのに、最初の質問のように・・・
char (*q)[100] = buf;
p = &q

これができない理由が・・・見当たらないです。
「データ構造が違うから初期化できない」
上記のプログラムができているので、むしろ初期化できないとおかしいような気がします。

[修正]

大変申し訳ありません。
int型とchar型を混合してしまいました。
修正しまいした。
すべてchar型です。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 9

+2

前の質問

    char buf[3][100] = {
        "you will make me happy",
        "I love you!",
        "Thanks"
    };

    char **p;
    char *q[3] = {buf[0], buf[1], buf[2]};
    p = q;

char **p = buf; こんな事をしたいというのがありましたが
char (*p)[100]=buf; こんな風に使います。

int main() {

    char buf[3][100] = {
        "you will make me happy",
        "I love you!",
        "Thanks"
    };

    char(*p)[100] = buf;
    printf("%s\n", p[0]);
    printf("%s\n", p[1]);
    printf("%s\n", p[2]);

    return 0;
}

ネットなどの多くの解説で「配列とポインタはよく似ている」「配列でもポインタでも同じ処理を書ける」などの文章から感覚が同じものの様になっているのではないでしょうか。
その結果が
char **p = buf;
2次元配列をダブルポインタに入れようとしていることではないかと思ってしまいました。
配列とポインタは相互に入れ替えできる部分はあっても別物です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/23 12:43

    ありがとうございます!!
    これは2次元配列で初期化できるよ。
    ということですよね。

    キャンセル

  • 2017/01/23 12:47

    確かに、2次元配列で初期化するときは、
    char *q[3] これより、char (*q)[3]の方が簡単にできますね!!

    キャンセル

  • 2017/01/23 15:55 編集

    追記について
    >arpという1つのポインタが4つの配列の先頭アドレスを指しているということですよね?
    >たった1つしかないポインタが同時に4つのアドレスを指しているなんて・・・
    いいえ違います。4つの要素を持つ配列の先頭アドレスを指しています。
    そしてこの場合、その4つの要素は配列です。
    arp[0]はint型の要素を10個持つ配列の先頭アドレスを
    arp[1]は次のint型要素を10個持つ配列の先頭アドレスを指します。。。
    (*p)[]の記述が配列へのポインタというのはそういう事です。

    キャンセル

  • 2017/01/23 16:11

    「4つの要素を持つ配列の先頭アドレス」
    ほうほう!
    ふむふむ・・・
    なるほど。
    分かりやすいです!

    キャンセル

  • 2017/01/24 19:52

    確かにポインタと配列は似ています。似ているというかコンパイラが吐き出すコードにしたら同様です。
    Cのコードによっては同じものを吐き出すでしょう。
    だからポインタと配列が同じなら[][]を**に置き換えて何が悪いのという質問者の感覚は理解できます。
    しかし、Cのコード上でポインタと配列は少し扱いが違います。
    Cはプログラマがやりたい事を邪魔しない思想の言語ですから、こんなこともできるのですが。。。

    #define PSIZE 4

    int main() {

    char buf[3][100] = {
    "you will make me happy",
    "I love you!",
    "Thanks"
    };

    char(*p)[100] = buf;
    printf("%s\n", p[0]);
    printf("%s\n", p[1]);
    printf("%s\n", p[2]);

    char **qq = (char**)buf;//サイズが違うが。。。
    printf("%s\n", qq);
    printf("%s\n", qq+((100/PSIZE)*1));//分かっているなら。。。
    printf("%s\n", qq+((100/PSIZE)*2));

    return 0;
    }

    こんな事を推奨しているわけではなく、勉強のためのコードです。
    配列は配列らしく、ポインタはポインタらしく使うのがいいと思っているので。

    キャンセル

  • 2017/01/24 20:07

    ほえ!!
    char **qq = (char**)buf;
    これってできるんですね!!

    ありがとうございます!

    キャンセル

  • 2017/01/24 20:25

    printf("%s\n", qq);
    これは、warningが出現します。
    実行はできていますね!

    キャンセル

+2

こんにちは。

普通に、int *arp = array_ten; で良いような気がします。 

私もそう思います。

折角頑張って要素数を付けても、C/C++では直ぐに要素数が抜け落ちて単なる要素へのポインタに成り下がってしまいますので、コンパイル・エラーの元になりがちです。

C++で特殊なケースで使ったことありますが、上記問題で頭が痛かったことがありました。

printf("p[1] : %p, p[1][0] : %p\n", p[1], &p[1][0]); 
おまけに2次元配列のようにアクセスもできています・・・・!? 

pは「char型10個」へのポインタです。ポインタは配列に読み替えることができますね。なので、p[1]はp[0]の「次」の「char型10個」の要素です。
「char型10個」は10バイトですからp[0]のアドレスに+10したらp[1]のアドレスになります。

まじで解りにくいですね。可能な場合はstruct element { char data[10]; };などとして、element* p;と定義した方が読みやすいし、バグりにくいと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/23 12:52

    ありがとうございます。
    char (*p)[10]; // 初期化なし
    printf("p[1] : %p, p[1][0] : %p\n", p[1], &p[1][0]);

    初期化なしで、2次元配列のようにできたので・・・あれ?と思ってしまいました。

    「ポインタは配列に読み替えることができます。」
    確かに、そうでしたね!

    キャンセル

  • 2017/01/23 12:58

    でも、これって、極端な話をすれば、
    printf("p[1000] : %p, p[1000]); みたいなこともできるんですよね?

    「列の方が可変で、行の方が固定の2次元配列」というイメージを持っていました。

    キャンセル

  • 2017/01/23 16:18

    できる筈ですよ。普通の配列(ポインタ)と同じですから。
    そのイメージで正しいです。C言語のポインタ≒可変長配列(領域管理はプログラマの責任)ですから。

    キャンセル

  • 2017/01/23 16:25

    なるほどです!

    キャンセル

+2

int型は4バイト、int[10]型は40バイトだから

int型のポインタはポインタ演算で4byte進もうとするけど
int[10]型のポインタは40byte進もうとするんだ

#include <iostream>
using namespace std;

int main() {
    int *singlep = 0;
    int (*arrayp)[10] = 0;
    cout << (unsigned int)singlep << endl << (unsigned int)(singlep+1) << endl;
    cout << (unsigned int)arrayp << endl << (unsigned int)(arrayp+1) << endl;
    return 0;
}


この小さなコードを実行してみて欲しい。
こんな感じで結果がでるはずだ。

0
4
0
40


ポインタにも細かい型の違いがあるんだよ
ポインタと配列が違うっていう人もいるけど、この動きは配列の時でも何も変わらない

#include <iostream>
using namespace std;

int main() {
    int array[2][10] = 0;
    cout << (unsigned int)array << endl << (unsigned int)(array+1) << endl;
    return 0;
}

> 4290089120
> 4290089160

追記


ポインタはアドレスの先頭を指している。一つのint型は4バイト使うので、もしも隣接する要素もint型なら次の4バイトは4バイト先のアドレスから始まる。型の大きさはプログラミングする上で重要な要素なんだ。受け取ったポインタが配列だった場合、次の要素の位置を簡単に指し示すことができるからね。

#include <iostream>
using namespace std;

int main() {
    char str[12];
    char *p = str;
    *(p+0) = 'H';
    *(p+1) = 'E';
    *(p+2) = 'L';
    *(p+3) = 'L';
    *(p+4) = 'O';
    *(p+5) = ' ';
    *(p+6) = 'W';
    *(p+7) = 'O';
    *(p+8) = 'R';
    *(p+9) = 'L';
    *(p+10) = 'D';
    *(p+11) = '\0';
    cout << str;
    return 0;
}

> HELLO WORLD
#include <iostream>
using namespace std;

int main() {
    char str[3][100] = { "Line1", "Line2", "Line3" };
    char *p = (char *)str;
    cout << p << endl << p+1 << endl;

    p = (char*)((int)p + 100);
    cout << p << endl << endl;

    char (*arrayp)[100] = str;
    cout << *arrayp << endl << *(arrayp+1) << endl;

    return 0;
}

> Line1
> ine1
> Line2

> Line1
> Line2


型情報には、アドレスをどうシフトさせたら次の要素が参照できるのかが得られるだけの差別化がある。
**ポインタのポインタは4バイトシフトするポインタだが、要素数100のchar型の配列のポインタは100バイトシフトするポインタなわけで、全然別ものなんだよね。

#include <iostream>
using namespace std;

int main() {
    char *p = 0;
    char **pp = 0;
    char (*ap)[100] = 0;
    cout << (unsigned int)(p+1) << endl;
    cout << (unsigned int)(pp+1) << endl;
    cout << (unsigned int)(ap+1) << endl;

    return 0;
}

> 1
> 4
> 100

更に追記

どんな時に使うのかが質問だった。

まず、char **pchar (*p)[100]の違いはなんですかって質問を見つめなおそう。
こいつはね、ポインタのポインタ配列のポインタの違いを聞いてる。
これはもっと抽象化すると、ポインタのポインタある型のポインタの違いを聞いてる。
これがわかるかどうかはstrike君次第だけど…
記法よりも、何をしているのかに着目してほしい。
これはプログラミングっていうより国語の問題だ。

ポインタのポインタある型のポインタは根本的に別物だよ。
char **pint *pの違いはなんですかって言ってるのと本質的には大差無い。

ある型のポインタには配列の型も指定できるんだよ。それがchar (*p)[100]って記法だ。
だから、あるポインタをある配列のポインタに変換することもできる。

char c[100*5];
char (*ap)[100] = (char(*)[100])c;

char a[5][100];
char *cp = (char*)a;


このコードはコンパイル通るし、お互いの範囲内で使う分には安全だよ。
cもaも変数としては連続した500バイトのメモリアドレスを確保していてどのようにアクセスするかが違うだけだ。

下記は今じゃまず使わないけどこのサンプルで配列型のポインタの使い方を示しているつもりだ。

#include <iostream>
using namespace std;

void char100print(char (*str)[100], int length)
{
    for (int i = 0; i < length; i++)
    {
        cout << *str++ << endl;
    }
}

int main() {
    char value[][100] = {"test1", "test2", "test3", "test4"};

    char100print(value, 3);

    return 0;
}

> test1
> test2
> test3

より具体的なサンプルも示そう。
昔は固定長のファイルが多かった。
今でも現役の固定長フォーマットに全銀協制定フォーマットがある。
銀行でお金を振り込んでくださいってことをするファイルだ。
これは120バイト(固定)を1行として使うファイルなんだ。
ファイルの読み書きは行単位ではなく、120バイト単位にしなきゃならない。

ZENGIN ReadZenginFormat(const char *p, int record_num);

ZENGIN ReadZenginFormat(const char (*p)[120], int record_num);

このサンプルは上下で情報量が全然違うよね。
でも動作原理はまったく一緒だし、読み取るための関数だから参照先アドレスを編集することがないのでconst属性がついてる。
record_num=5の場合、pはとあるアドレスを参照しており、そこから連続した600バイトのメモリが確保されている。

一方、以下は全く意味が異なる。

ZENGIN ReadZenginFormat(const char **p, int record_num);


pは…ポインタのポインタの配列なんだろう。読み取り専用だしね。
でもそうなると、ポインタそのものは5行であったとしたら、pのアドレスから確保されているバイト数はわずか4*5バイト=20バイトだ。
その20バイトの中に、5つの文字列のポインタが入っているわけだ。

使い方を示すとこうだ。

char *p[5];
p[0] = malloc(120);
p[1] = malloc(120);
p[2] = malloc(120);
p[3] = malloc(120);
p[4] = malloc(120);

ReadZenginFormat(p, 5);

ポインタそれぞれに別の文字列の参照を持つ。
p[0]からp[4]で参照しているメモリアドレスは別に連続したメモリ空間じゃない。
一旦次のポインタに移動して、そこから別の文字列を参照する。
これは配列のポインタとは構造が全く異なるんだよ。

ポインタのポインタ配列のポインタには互換性が無いんだ。
互換させる場合はこうなる。

char a[5][120];
char *p[5];
p[0] = a[0];
p[1] = a[1];
p[2] = a[2];
p[3] = a[3];
p[4] = a[4];

ReadZenginFormat(p, 5);


わかるかな。
a[0]は先頭アドレスを指している。
a[1]はその120バイト先のアドレスを指しているが、a[1]には大きさがあるわけではない。
a[1]で指示される場所が120バイト先のアドレスというだけだ。
一方、p[1]はアドレスを保存するための変数として4バイト確保されている。
そこに代入している。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/24 16:56

    ありがとうございます!

    キャンセル

  • 2017/01/24 17:38 編集

    char *p = 0; // p+1 : 1
    char **pp = 0; // pp+1 : 4
    char (*ap)[100] = 0; // ap+1 : 100

    p = &p; 可能!
    pp = &ap; 不可能!

    違いがいまいちわからないのですが・・・

    キャンセル

  • 2017/01/24 20:12

    非常に説明しづらいけどなんとか書いてみました。
    strike1217君はそもそもとんでもなく勘違いしてます。
    もっと確保したメモリがどうなっているか考えてみてください。

    キャンセル

  • 2017/01/24 20:16

    どこかで直接通話できれば図とか書いて教えてあげれるから早いんだけどね。

    キャンセル

  • 2017/01/24 20:20

    ポインタのポインタはポインタのアドレスを格納できます。
    配列のポインタのアドレスもポインタのポインタに格納できる!

    ・・・と思っていたんですが・・・

    キャンセル

  • 2017/01/24 20:29

    うーん、使い方がぜんっぜん異なるからね。

    可能不可能の問題では可能だけど、やっても次のアドレスみれないよ?

    ある配列char a[2][100]のポインタのアドレスが10000だとしよう。
    で、ポインタのポインタpに同じ10000って値を入れることはできそうだよね。
    しかも*pってアクセスすればアドレス10000から始まる文字列にはアクセスできそう。

    でも、だからってa[1] は10100をさすけど
    p[1]で10100をさすことはできない、代入しない限りp[1]は不定値だし、アクセスすればsegmentation faultだ。

    あれなら、あとで絵にかくよ。

    キャンセル

  • 2017/01/24 20:34 編集

    確かに、以下をテストするとおちます。

    char iii[3][100] = {
    "you will make me happy",
    "but I dont like you!",
    "Good Bye!"
    };

    char (*qqq)[100] = iii; // 配列のポインタ
    char **ppp = &qqq; // warningが出てくる(なんで?)

    printf("%s\n", *ppp); // ok!
    printf("%s\n", *(ppp+1)); // 落ちる!

    キャンセル

  • 2017/01/24 20:42

    そりゃそうさ、pppはただのアドレスの参照だもの。
    あるメモリpppの次のバイトってどうなってるって考えてみよう。
    strike1217君のメモリレイアウトがそのまま確保された場合、pppの次のバイトは不定値。
    ppp+1で次のバイトを見たら不定値。
    printfでそこを見たら当然落ちる。

    互換性まったくないんだからね、暗黙的な返還はできないよ。
    iii[3][100]は300バイト確保される。
    この意味をよく考えてみて。
    iii[1]はiii[0]から100ずれたアドレスを指してるだけ。
    場所の情報。
    ppp[1]はpppから1ずれたアドレスを指してるだけ。
    これはpppが指し示すiii[0]のさすアドレスの1バイトずれたアドレスじゃなくて
    ppp自体のアドレスの1バイトずれたアドレス。

    互換性無いのに暗黙的に変換できるわけがないよね。

    キャンセル

  • 2017/01/24 20:45

    型が先にあって、アドレスの操作は型に紐づいてるんであって、参照先には紐づかないよ。

    キャンセル

  • 2017/01/24 20:48

    あは~~~!!
    printf("%s, %s\n", *ppp, *ppp+100);
    こいつは問題がありませんでした。

    you will make me happy, but I dont like you!  ← 目的の結果になりました。

    *(ppp+1) にすれば、 *ppp + 100にアクセスできると思い込んでいました。

    キャンセル

  • 2017/01/24 20:53

    そう、それが違うわけ。どう見てもポインタのポインタの型情報には100バイトシフトして、なんて情報はいってないよね。どうやっても暗黙的には変換できないんだよ。

    キャンセル

  • 2017/01/24 21:03

    「100バイトシフトして、なんて情報はいってない」
    ああ~~
    なるほど!
    できない理由がやっとわかりました。

    キャンセル

  • 2017/01/25 19:06 編集

    横槍失礼。
    再確認。ppp は qqq という変数をポイントしています。
    *ppp とは何か? 答えは qqq の値です。
    そして、qqq の値とは "you will ..." という文字列の先頭アドレスです。

    要するに、*ppp は "you will make me happy" の先頭アドレスであり、
    それに 100 を足せば "but I dont like you!" の先頭アドレスになります。
    ですから printf("%s", *ppp + 100); は "but I dont..." を表示します。
    これはよいですね。

    ところで、一般的に言って、*p とは p[0] と書けます。
    従って、printf("%s", *ppp); は printf("%s", ppp[0]); と同じです。
    これもよいですね?

    > *(ppp+1) にすれば、 *ppp + 100にアクセスできると思い込んで

    では、*(ppp + 1) は何か?
    一般的に、*(p + 1) は p[1] と同じですから、*(ppp + 1) は ppp[1] とも書けます。
    これも、理解できますよね?
    以上が意味する事は、こういう事です。

    ppp が指すメモリは qqq である、
    そして、その付近のメモリは qqq 自身のアドレスを先頭にして、
    「ポインタが配列のように並んでいる」と見做すことができる。
    念の為、ppp[0] は qqq それ自身です。
    で、
    *(ppp + 1)、即ち ppp[1] とは、qqq というポインタ変数の「隣のメモリ」なのです!

    「qqq の隣のメモリ」が、何に使われているか、不明です。"無理やり"ポインタだと見做した場合、何番地を指しているかわからない。不定値です。少なくとも、"but I dont like you!" を指してはいません。
    よって、落ちても不思議は無い(運が良ければ、何かゴミ文字列が表示される)。

    これが理解できると、貴方の勘違いも、落ちる原因も、全て解決するはずです。

    当然ですが、単純変数も配列も、ポインタも、全てメモリ上に配置されています。
    できれば、ご自分のプログラム中の各変数が何番地に割り当てられているか、
    メモリダンプなど、何らかの方法で確認してみる事をお勧めします。
    次第に、こうしたことが実感できてくるはずです。

    P.S.
    > char **ppp = &qqq; // warningが出てくる(なんで?)

    念の為。これは昨日の私の回答の中で触れた通り、コンパイラは「型が違う」と言っている。ので、キャストすると warningは消えます。
    char **ppp = (char**) &qqq;

    キャンセル

  • 2017/01/25 20:06

    ありがとうございます!

    qqqで、pppを初期化したら、2次元配列のように、列x行で確保できていると勝手に思っていました。
    qqq は列x行に伸びているわけではないんですよね?
    qqqは一次元配列ですよね?

    キャンセル

  • 2017/01/25 20:20

    qqqは配列ですらないよ 100バイトのchar型配列のポインタだから、変数としてみるならわずか1個だよ

    キャンセル

  • 2017/01/25 20:22

    qqq+1は運が悪ければその時点でsegmentation faultだし、どっちにしろ大体そこに格納されてる不定値のせいでsegmentation faultだ。何度もいってるけど、char (*qqq)[100]はchar(*)[100]型の1個の変数だよ。int a;やint *bを配列って言わないよね?

    キャンセル

  • 2017/01/25 20:30

    あ!qqqはポインタですね

    キャンセル

  • 2017/01/25 20:36

    今やってみたところ、

    printf("%s", qqq + 1);
    これは普通にできるみたいですが・・・

    キャンセル

  • 2017/01/25 20:39

    qqqは一個の単純なポインタ変数です。配列ではないのに、 *(ppp + 1) とアクセスしたために、その辺りを、あたかも配列であるかのようにアクセスしてしまう・・・一種のバッファオーバーランとも言えるから、その時点で不正なメモリアクセスです。
    ことほどさように、自分が今何をしてるのか、明確なイメージを持ってないと危ないのがC言語です。切れ味するどいツールは、扱いが難しいのです。

    キャンセル

  • 2017/01/25 20:44

    printfは受け取った変数の数値を、左側の文字列から判定しているだけ。
    つまり、printf("%s", some_value);だったらsome_valueの型がなんであろうと文字列(char*)として処理しているだけ。だからqqqの型は関係ないんだよね。printfで表示する時にはすでに失われてて、変数の値だけ受け取ってるんだよ。qqq+1が指し示すアドレスが文字列をきちんと指すから問題ない。

    特に不思議な魔法はそこには何もない。

    キャンセル

  • 2017/01/25 20:45 編集

    > printf("%s", qqq + 1);
    > これは普通にできるみたいですが・・・

    ああ、そうですね。
    char (*qqq)[100] = iii; // 配列のポインタ
    即ち、一要素(qqqが指す先)が100バイトだと宣言してあるから、+1 でアドレスが100バイト進むわけです。
    それ、即ちアドレスが1バイトずつ進む char** という型との違いなのですよ。

    キャンセル

  • 2017/01/25 20:48

    char (*qqq)[100] = iii;

    2次元配列で初期化しているから、あたかも2次元配列のようにアクセスできるのですね!!

    キャンセル

  • 2017/01/25 20:51

    2次元配列も連続したバイト配列を確保しているだけだからね…

    char a[3][100]はアドレスaから1文字列目100バイト、2文字列目100バイト、3文字列目100バイト確保されて連続した300バイトになってるだけだから、ちゃんと100バイトずつずらせばn文字列目っていうのを参照できるようになってます。だからchar(*)[100]型のポインタは加算すると100バイトずらしてくれる。もしも受け取ったポインタが配列のアドレスだった場合のためにね。

    キャンセル

  • 2017/01/25 20:52 編集

    >2次元配列で初期化しているから、あたかも2次元配列のようにアクセスできるのですね!!

    それは違う。まだ分かってないな。残念w

    char x = 'A';
    char *p = &x;
    printf("%c", p[1]);

    pが指してる先を配列にしたつもりはないのに、ポインタだから、その辺りをあたかも配列のようにアクセスできてしまう、というだけ

    キャンセル

  • 2017/01/25 20:54

    ガーーーーン!
    それはちがうんですか!

    キャンセル

  • 2017/01/25 20:55

    「2次元配列も連続したバイト配列を確保しているだけだからね」
    ああ~~
    そうなんですね!

    よくある、平面のような図を想像していました。

    キャンセル

  • 2017/01/25 21:05

    char *p = &iii[0][0];

    char (*w)[100] = iii;
    下の方がアクセスが簡単にできますね。

    w + 1 == p + 100 になってました。

    キャンセル

+1

intと同じでよろしければ

    int (*arp)[10];

    int array_ten[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    arp = &array_ten;
    printf("%d \n",(*arp+1)[2]);

    char buf[3][100] = {
        "nononono!!",
        "I love you!",
        "Thanks"
    };

    char (*q)[3][100];
    q = &buf;
    printf("%s \n",(*q)[1]);

  char **p = (char **)(*q)[1];
    printf("%s \n",p);

int (*arp)[10];arp1は、intの配列(要素10)へのポインタなので、代入の場合キャスト(int (*)[10]) intの配列(要素数10)でポインタに変換する。

int main() {

 char ii[4] = "GHYU";
 char *qq = ii;
 char **pp = &qq;
 int array[4][10]={{1,2,3,4,5,6,7,8,9,0},{10,11,12,13,14,15,16,17,18,19}};
 int (*arp)[4][10] = (int (*)[4][10])array;
 printf("%d \n", (*arp)[0][5]);
}

実はこの方のページ を参考とさせて頂いています。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/23 12:38

    ありがとうございます。
    ポインタのポインタの初期化については

    char (*q)[100];
    q = &buf;

    これで終わりなら、2次元配列で初期化して終わり!って感じですが・・・・
    問題は、
    char **p = q; こいつができないということです。

    char *q;
    char **p = q; こっちはおkでした。

    キャンセル

  • 2017/01/23 12:41 編集

    あ!
    失礼しました。
    「char (*q)[100]; の初期化に2次元配列を使用できますよ!」
    ってことですね!

    キャンセル

  • 2017/01/24 14:20

    char **p = (char **)(*q)[1];

    私の環境で、できませんでした。
    コンパイラがエラーを吐き出します。

    char **p = (char **)(*q);
    こちらは、大丈夫でしたが、実行すると落ちます・・・

    キャンセル

  • 2017/01/24 14:41

    Centos7(gcc 4.4.7) ,Centos6(gcc 4.1.2 )、Centos5(32)ではコンパイルも実行もでき”I love you!” が表示されます。

    キャンセル

  • 2017/01/24 14:44

    ほえ!
    本当ですか!
    もう一度やってみます!!

    キャンセル

  • 2017/01/24 14:52

    warning C4477: 'printf' : format string '%s' requires an argument of type 'char *', but variadic argument 1 has type 'char **'

    同じようにやると上記のようなwarningが出現し、今度は文字化けします。

    キャンセル

  • 2017/01/24 14:54

    printf("%s \n", p); これだとwarningが出ますが、うまくいきます。

    printf("%s \n", *p); こっちにしたら、 warningは消えて、落ちます・・・・

    キャンセル

  • 2017/01/24 15:53

    ubuntu(gcc v5.3.1)だとワーニングがでました。
    warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char **’ [-Wformat=] printf("%s \n",p);
    なのでcastする事にしました。
    printf("%s \n",(char *)p);

    キャンセル

  • 2017/01/24 16:06

    ほうほう!

    char * でキャストということは、結果としてはポインタのポインタではなくなるということですかね?

    キャンセル

  • 2017/01/24 16:13

    ポインタのポインタで有る事には、変りわないがコンパイラが型が異なる事をワーニングしてくれているのでは、ないかと思もっています。printの%sにcharのアドレスを渡す事ができれば良いのかと考えています。

    キャンセル

  • 2017/01/24 16:21

    ちょっと、追記してみました。
    どうして、できないのかが、わからないです・・・

    キャンセル

  • 2017/01/24 17:40 編集

    追加の
    char ii[4] = "GHYU";
    char *qq = ii;
    char **pp = &qq;
    これは、できましたか??
    warning が出てきて、printf();などでアクセスすると落ちるんですよ。

    キャンセル

  • 2017/01/24 17:44

    char (*q)[3][100];
    これが唯一違うんです。

    char (*q)[100];
    私は、こっちでやってました。
    char (*q)[3][100]; は さらに次元が上がって3次元になるんではないのでしょうか??

    キャンセル

  • 2017/01/24 17:54

    配列値を指すアドレス全てを配列に入れるので同じ形が必要の様です。[40]とするのは駄目の様です。

    キャンセル

  • 2017/01/24 18:06

    これはできる様です。
    int (*arp)[10];
    arp = (int (*)[10])array[1];

    キャンセル

  • 2017/01/24 18:25

    ああ~~
    そうなんですか!

    なんか、すごく難しいんですが・・・

    キャンセル

+1

これをやると、warningが出てきて、さらに落ちます・・・・ 
なぜかわかりませんが・・・

手元のGCCでコンパイルするとp = q;の行で、
"warning: assignment from incompatible pointer type ..." となりました。「pとqでは互換性が無い(?)のに、代入して大丈夫なのか?」という警告です。要するに「型が違う」のだから、warningを消す手立ては、まずキャストでしょう。即ち、

    char **p;

    p = (char**)q;     // キャストする

これでwarningが消えますが、手立ては他にもあります。ポインタpの宣言を変更すれば良い。即ち、

    char (*p)[100];        // p を、こう宣言する

    p = q;        // キャスト不要

以上は、char **char (*)[100]を、コンパイラは型として扱っている事、しかも明確に異なる型だと区別している事を示しています。

char **は、ポイントしてる先のcharが、1個なのか10個なのか、それとも100個なのか、サイズの指定はありません。
それに対して、char (*)[100]は、個々の要素は同じくcharだけど、「100個のcharをひとかたまり(100バイトを一つのデータ型)と見るポインタ」だという宣言です。こちらにはサイズの指定があるので、区別がつきます。

一般的に言えば、「固定長の配列」を一つの型として扱える…そう考えれば、そのようなデータ構造は、決して珍しくないはずですから、そうした管理がふさわしければ使う価値があると思います。ただ実際は、通常の(サイズ指定をしない)ポインタでアクセスも操作も可能なので、いまいち出番が少ないかもしれませんね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/23 21:07 編集

    ありがとうございます!
    サイズの指定があると区別されるんですね!!

    p = (char **)q;
    確かに、キャストするとwarningはでなくなります。
    しかし、
    printf("m: %s, %s\n\n", *m, *(m + 1)); このようにアクセスすると落ちます。

    *m の方は大丈夫のようですが、*(m+1)にするとダメでした。
    なぜでしょうか?
    アクセスの仕方が混乱していますが、間違っていますかね?

    キャンセル

  • 2017/01/23 21:14

    その「m」、唐突に出てきましたが、どう宣言し、どう初期化しましたか?

    キャンセル

  • 2017/01/23 21:15

    あ!
    ごめんなさい。

    mは名前を変更しただけです。
    質問に出てくるpと全く同じです。
    すいません。

    キャンセル

  • 2017/01/23 21:26

    ああ、私もうっかりしていました。
    char **p; ではなく char *p; とすべきで、従って p = (char*) q; として
    printf("p: %s, %s\n\n", p, p + 1); ではありませんか?

    キャンセル

  • 2017/01/23 21:31 編集

    もしくは
    char (*p)[100]; と宣言して p = q; とし、
    printf("p: %s, %s\n\n", (char*)p, (char*)(p + 1));
    これで
    p: nononono!!, I love you!
    と出力しました。この出力を期待してるんじゃないかな?

    キャンセル

  • 2017/01/23 21:36

    ああ~~
    確かに、できますね。
    この方法も1つありと言うことですよね・・・

    私は、ポインタのポインタの初期化の仕方を char (*q)[100]; でやれば、char *q[3]; これより簡単にできるのではないか・・・・と考えていたんです。

    なので、ポインタのポインタを使わないと、お勉強にならないのです!
    char **p; こいつを char (*q)[100]; での初期化方法はわかりますか?

    キャンセル

  • 2017/01/23 21:41

    さあ、よくわかりません。
    やりたいことを整理して、新たな質問にしたほうが良いのでは?

    キャンセル

  • 2017/01/23 21:44

    んん~~

    やはり、これはできなんですかね・・・・・
    ポインタのポインタの初期化の仕方が・・・わかりにくいですね。

    キャンセル

  • 2017/01/23 21:56

    「ポインタのポインタの初期化」で、思ったんですが、
    main(int argc, char **argv) は、ご存知?
    char **argv は char *argv[] でもありますが、それはさておき、
    char **argv と宣言するポインタが、どんなデータ構造をポイントしているか、絵に描いてみてください。それが身近なポインタのポインタです。そんなデータ構造を考えてみればよいのでは?

    キャンセル

  • 2017/01/23 22:00

    char **argv は char buf[3][100] のような配列をポイントしているのではありません。何が違うか、その違いを図に描いて体得するのが大事だと思います。

    キャンセル

  • 2017/01/23 22:09 編集

    ああ、まさに
    char *q[3] = { buf[0], buf[1], buf[2] }; // こっちは成功
    char **p; // ←これがポインタのポインタ
    として
    p = q;   // これで初期化ができる
    「char **argv」も、この char **p と同じ(よく似た)構造をポイントしている

    キャンセル

  • 2017/01/24 14:12

    ふむふむ
    ありがとうございます!

    キャンセル

  • 2017/01/24 16:57 編集

    ポインタのポインタの初期化なので
    p = &q;
    こちらが正しかったです。
    修正しました。
    これでもできませんでしたが・・・

    キャンセル

0

具体的にどんな時に使うかは人それぞれだと思いますが、
charポインタを10個ほしい時に使います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/23 12:20

    それは、おそらくこちらではないでしょうか?
    char *p[10];

    キャンセル

  • 2017/01/23 12:36

    ご指摘、ありがとうございます。
    勘違いしてました。申し訳ないです。

    キャンセル

  • 2017/01/23 12:39

    大丈夫です!!

    キャンセル

0

ポインターのポインターでありさえすればいいのであれば
int **argp;
でよいわけですが、「10要素の配列へのポインター」であることを明確にしたいときに有用です。
int (*argp)[10];
と宣言することで10要素でない配列へのポインターを代入しようとしたとき(つまりバグ)コンパラーが警告を発してくれます。

int b[9];
argp = &b; // <= 警告: 互換性のないポインタ型からの代入です

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/23 12:32

    ありがとうございます。
    要素数が9のbと要素数10のargpでは、たしかに互換性がないと思いますが・・・

    要素数が同じなのに、warningがでてきて、落ちるんですよ。
    おそらく、
    char (*q)[100] = buf; こいつを ポインタのポインタに入れるときがダメみたいです。
    なんでダメなのかわかりませんが・・・・

    キャンセル

0

たとえば列数固定の行列を確保したいとき。

  // N行10列の行列を確保する
  float (*p)[10];
  p = (float (*)[10])malloc(N * 10 * sizeof(float));
  ... N行10列の行列 p[i][j] として使う
  free(p);

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/23 13:54

    ふむふむ・・・
    ありがとうございます。
    動的確保の時の使用例ですね!!

    キャンセル

0

やはり、char (*p)[10];

1、列の動的確保
2、2次元配列で初期化できる → 関数に2次元配列を渡す!

この2つくらいしか、使い道が思いつきません・・・です。 

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 90.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • C

    3956questions

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

  • C++

    3737questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。