web-dev-qa-db-ja.com

C言語で**すること

Javaの良い背景を持つCの初心者で、ポインターと配列を理解しようとしています。

添え字operator[]は配列定義の一部なので、次のとおりです。

int numbers[] = {1,3,4,5};

整数配列を作成します。これは、メモリ内で16バイト、4ロットの4バイトとして表されます。

numbers[0] = 1, address 0061FF1C
numbers[1] = 3, address 0061FF20
numbers[2] = 4, address 0061FF24
numbers[3] = 5, address 0061FF28

ただし、ポインターのことになると、私の知識が崩れ始めます。したがって、配列番号へのポインターを作成する場合、次のようにします。

int *pNumbers = &numbers[0];

次のようになります。

pointer to numbers

そして、私はそれが4バイトのサイズになると推測していますか?

しかし **私は「ポインタへのポインタ」と読みますが、それは私には意味がありません。なぜ誰かがポインタへのポインタを必要とするでしょうか。私は何かが欠けていることを知っています、argvchar[ ] または char **以下に見られるように:

int main(int argc, char **argv){}

そう:

  • これは何ですか (**)?
  • どんな用途がありますか?
  • それはメモリでどのように表されますか?
36
James

Cでは、引数は値によって渡されます。たとえば、mainに可変整数がある場合

int main( void )
{
    int x = 10;
    //...

そして、次の機能

void f( int x )
{
    x = 20;
    printf( "x = %d\n", x );
} 

次に、このようにメインで関数を呼び出すと

f( x );

次に、パラメータはmainの変数xの値を取得します。ただし、パラメータ自体は、メモリ内で引数とは異なる範囲を占有します。したがって、関数内のパラメーターの変更は、メインの元の変数に影響を与えません。これらの変更は異なるメモリ範囲で発生するためです。

それでは、関数のmainの変数を変更する方法は?

ポインターを使用して変数への参照を渡す必要があります。

この場合、関数宣言は次のようになります

void f( int *px );

そして、関数定義は

void f( int *px )
{
    *px = 20;
    printf( "*px = %d\n", *px );
} 

この場合、元の変数xによって占有されているメモリエクステントが変更されます。関数内でポインターを使用してこのエクステントにアクセスできるためです

    *px = 20;

当然、関数はメインのように呼び出す必要があります

f( &x );

ポインターpxであるパラメーター自体が通常の関数のローカル変数であることを考慮してください。つまり、関数はこの変数を作成し、変数xのアドレスで初期化します。

ここで、たとえば次のようにポインターを宣言したと仮定しましょう

int main( void )
{
   int *px = malloc( sizeof( int ) );
   //..

そして、次のように定義された関数

void f( int *px )
{
    px = malloc( sizeof( int ) );

    printf( "px = %p\n", px );
}

パラメーターpxはそれに割り当てられるローカル変数であるため、値は元のポインターに影響しません。この関数は、mainの元のポインターpxが占めるエクステントとは異なるメモリのエクステントを変更します。

関数内の元のポインターを変更する方法は?参照で渡すだけです!

例えば

f( &px );
//...

void f( int **px )
{
    *px = malloc( sizeof( int ) );

    printf( "*px = %p\n", *px );
}

この場合、元のポインターに格納された値は関数内で変更されます。これは、逆参照を使用する関数が元のポインターが定義されたのと同じメモリエクステントにアクセスするためです。

32

Q:これは何ですか(**)?

A:はい、まさにそれです。ポインターへのポインター。

Q:どんな用途がありますか?

A:多くの用途があります。特に、2次元データ(画像など)を表現する場合。あなたの例の場合char** argvは、charsの配列の配列と考えることができます。この場合、各char*は、文字列の先頭を指します。このように、実際に自分でこのデータを明示的に宣言できます。

char* myStrings[] = {
    "Hello",
    "World"
};

char** argv = myStrings;

// argv[0] -> "Hello"
// argv[1] -> "World"

配列のようなポインターにアクセスする場合、インデックスを付けた数値と要素自体のサイズを使用して、配列内の次の要素のアドレスにオフセットします。また、すべての数字にアクセスすることもできます。実際、これは基本的にCが行っていることです。コンパイラは、intなどの型がコンパイル時に使用するバイト数を知っていることに注意してください。そのため、各ステップが次の要素までの大きさを知っています。

*(numbers + 0) = 1, address 0x0061FF1C
*(numbers + 1) = 3, address 0x0061FF20
*(numbers + 2) = 4, address 0x0061FF24
*(numbers + 3) = 5, address 0x0061FF28

*演算子は、参照解除演算子と呼ばれます。ポインターが指すメモリから値を取得するために使用されます。 numbersは、文字通り、配列の最初の要素へのポインターです。

私の例の場合、myStringsは、ポインター/アドレスが4バイトであると仮定すると、次のようになります。これは、32ビットのマシン上にあることを意味します。

myStrings = 0x0061FF14

// these are just 4 byte addresses
(myStrings + 0) -> 0x0061FF14 // 0 bytes from beginning of myStrings
(myStrings + 1) -> 0x0061FF18 // 4 bytes from beginning of myStrings

myStrings[0] -> 0x0061FF1C // de-references myStrings @ 0 returning the address that points to the beginning of 'Hello'
myStrings[1] -> 0x0061FF21 // de-references myStrings @ 1 returning the address that points to the beginning of 'World'

// The address of each letter is 1 char, or 1 byte apart
myStrings[0] + 0 -> 0x0061FF1C  which means... *(myStrings[0] + 0) = 'H'
myStrings[0] + 1 -> 0x0061FF1D  which means... *(myStrings[0] + 1) = 'e'
myStrings[0] + 2 -> 0x0061FF1E  which means... *(myStrings[0] + 2) = 'l'
myStrings[0] + 3 -> 0x0061FF1F  which means... *(myStrings[0] + 3) = 'l'
myStrings[0] + 4 -> 0x0061FF20  which means... *(myStrings[0] + 4) = 'o'
11
kirk roerig

argv引数を記述する従来の方法は_char *argv[]_です。これは、それが何であるか、文字へのポインターの配列(つまり、文字列の配列)についての詳細情報を提供します。

ただし、関数に配列を渡すと、ポインターに減衰し、charまたは_char **_へのポインターへのポインターが残ります。


もちろん、ポインターへのポインターを逆参照するときに二重アスタリスクを使用することもできるため、質問の最後にコンテキストを追加しないと、コンテキストに応じて、Cで_**_が何を意味するかという質問に対する2つの答えがあります。

argvの例を続けるには、argvの最初の要素の最初の文字を取得する1つの方法は、_argv[0][0]_、または_**argv_のように、逆参照演算子を2回使用できます。

配列のインデックス付けと逆参照は、ほとんどの場所で交換可能です。これは、任意のポインターまたは配列pおよびインデックスiの式_p[i]_は*(p + i)と同等です。 iが_0_である場合、*(p + 0)*(p)に短縮できます。これは_*p_と同じです。

好奇心として、_p[i]_は*(p + i)および commutative property の加算と同等であるため、式*(p + i)*(i + p)は、_p[i]_が_i[p]_と等しくなることにつながります。


最後に、ポインターの過度の使用に関する警告、フレーズ three-star Programmer が聞こえる場合があります。 _***_のような3つのアスタリスク(ポインターへのポインターへのポインターのように)。しかし、リンクから引用するには

明確にするために、ThreeStarProgrammerと呼ばれることは通常、compめ言葉ではありません

そして別の警告: 配列の配列はnotポインターへのポインターと同じです (古い回答へのリンク配列の配列の代替としてのポインターへのポインターのメモリレイアウトも表示します。)

宣言の**は、ポインターへのポインターを表します。ポインター自体はデータ型であり、他のデータ型と同様にポインターを持つことができます。

int i = 5, j = 6; k = 7;
int *ip1 = &i, *ip2 = &j; 
int **ipp = &ip1;  

enter image description here

ポインターへのポインターは、動的な2D配列を割り当てる場合に役立ちます。 10x10 2D配列を割り当てるには(連続していない場合があります)

int **m = malloc(sizeof(int *)*10;  
for(int i = 0; i < 10; i++)
    m[i] = malloc(sizeof(int)*10  

また、関数を介してポインターの値を変更する場合にも使用されます。

void func (int **p, int n)  
{
    *p = malloc(sizeof(int)*n); // Allocate an array of 10 elements 
}

int main(void)
{
    int *ptr = NULL;
    int n = 10;
    func(&ptr, n);
    if(ptr)
    {
        for(int i = 0; i < n; i++)
        {  
             ptr[i] = ++i;
        }  
    }

    free(ptr);
}

さらに読む: Pointer to Pointer

5
haccks

文字列のテーブルなど、ポインタのテーブルがあるかどうかを検討してください(「C」の文字列は、単に文字列の最初の文字へのポインタとして処理されるため)。

次に、テーブルの最初のポインターへのポインターが必要です。したがって、「char **」。

整数の2次元テーブルなど、すべての値を含むインラインテーブルがある場合、1レベルのインダイレクション(つまり、「int *」などの単純なポインタ)で逃げることは完全に可能です。ただし、最終結果に到達するために間接参照する必要があるポインターが中央にある場合、間接参照の第2レベルが作成され、ポインターからポインターが不可欠になります。

ここで別の説明。 「C」では、ポインター表記(例:「* ptr」)と配列インデックス表記(例えば、ptr [0])を使用した間接参照は、配列表記の明らかなインデックス値以外はほとんど違いがありません。アスタリスクとブラケットが本当に重要なのは、変数を割り当てるときだけです(たとえば、int * x;はint x [1]とは非常に異なります)。

4
ash

まず、Cは配列をJavaとは非常に異なる方法で処理することを忘れないでください。のような宣言

char foo[10];

10個のchar値とnothing else(アライメント要件を満たすために追加のスペースを調整)に十分なストレージを割り当てます。最初の要素または配列サイズや要素クラスタイプなどの他の種類のメタデータへのポインタ用に追加のストレージが確保されることはありません。配列要素自体以外にオブジェクトfooはありません1。代わりに、言語には、sizeofまたは単項&演算子(または文字列リテラル)のオペランドではない配列expressionをコンパイラが認識するルールがあります。宣言内の別の配列を初期化するために使用されます)、それは暗黙的にexpressionをタイプ「TのN要素配列」から「Tへのポインター」に変換し、式の値配列の最初の要素のアドレスです。

これにはいくつかの意味があります。まず、配列式を関数の引数として渡すと、関数が実際に受け取るのはポインター値です。

char foo[10];
do_something_with( foo );
...
void do_something_with( char *p )
{
  ...
}

実パラメータpに対応する仮パラメータfooは、charの配列ではなく、charへのポインタです。混乱させるために、Cではdo_something_withを次のように宣言できます。

void do_something_with( char p[] )

あるいは

void do_something_with( char p[10] )

ただし、関数パラメーター宣言の場合、T p[]およびT p[N]T *pと同一であり、3つすべてがpを配列ではなくポインターとして宣言します2。これは、関数パラメーター宣言にのみ当てはまることに注意してください。

2番目の意味は、添字演算子[]をポインターオペランドだけでなく配列オペランドでも使用できることです。

char foo[10];
char *p = foo;
...
p[i] = 'A'; // equivalent to foo[i] = 'A';

最後の意味は、ポインターへのポインターを扱う1つのケースにつながります。次のようなポインターの配列があるとします。

const char *strs[] = { "foo", "bar", "bletch", "blurga", NULL };

strsconst char *の5要素配列です3;ただし、次のような関数に渡す場合

do_something_with( strs );

関数が受け取るものは、実際にはポインターの配列ではなく、ポインターへのポインターです。

void do_something_with( const char **strs ) { ... }

ポインターへのポインター(およびより高いレベルの間接参照)は、次の状況でも表示されます。

  • ポインタ型のパラメータへの書き込み: Cはすべてのパラメータを値で渡すことに注意してください。関数定義の仮パラメーターは、関数呼び出しの実際のパラメーターとはメモリ内の異なるオブジェクトです。そのため、関数で実際のパラメーターの値を更新する場合は、pointerそのパラメーターに:

    void foo( T *param ) // for any type T
    {
      *param = new_value(); // update the object param *points to*
    }
    
    void bar( void )
    {
      T x;
      foo( &x );   // update the value in x
    }
    
    今、タイプTをポインタータイプR *に置き換えると、コードスニペットは次のようになります。

    void foo( R **param ) // for any type R *
    {
      ...
      *param = new_value(); // update the object param *points to*
      ...
    } 
    
    void bar( void )
    {
      R *x;
      foo( &x );   // update the value in x
    }
    
    同じセマンティクス-xに含まれる値を更新しています。この場合、xには既にポインター型があるため、ポインターにポインターを渡す必要があります。これは、より高いレベルの方向に拡張できます。

    void foo( Q ****param ) // for any type Q ***
    {
      ...
      *param = new_value(); // update the object param *points to*
      ...
    } 
    
    void bar( void )
    {
      Q ***x;
      foo( &x );   // update the value in x
    }
    
  • 動的に割り当てられた多次元配列:Cで多次元配列を割り当てる一般的な方法の1つは、ポインターの配列を割り当て、その配列の各要素にポインターが指すバッファーを割り当てることです:

    T **arr;
    arr = malloc( rows * sizeof *arr );  // arr has type T **, *arr has type T *
    if ( arr )
    {
      for ( size_t i = 0; i < rows; i++ )
      {
        arr[i] = malloc( cols * sizeof *arr[i] ); // arr[i] has type T *
        if ( arr[i] )
        {
          for ( size_t j = 0; j < cols; j++ )
          {
            arr[i][j] = some_initial_value();
          }
        }
      }
    }
    
    これはより高いレベルのインダイレクションに拡張できるため、T ***T ****などのタイプがあります。

1.これは、配列式が割り当てのターゲットにならない理由の一部です。 を割り当てるものは何もありません。

  1. これは、Cの派生元であるBプログラミング言語からの引き継ぎです。 Bでは、ポインターはauto p[]として宣言されます。

  2. 各文字列リテラルはcharの配列ですが、charの個々の配列を初期化するために使用していないため、式はポインター値に変換されます。

4
John Bode

あなたが言う_int *_の例

そして、私はそれが4バイトのサイズになると推測していますか?

Javaとは異なり、Cはそのデータ型の正確なサイズを指定しません。実装ごとに異なるサイズを使用でき、実際に使用します(ただし、各実装には一貫性が必要です)。最近では4バイトのintsが一般的ですが、intsは2バイトと同じくらい小さい場合があり、本質的に4バイトに制限されるものはありません。ポインターのサイズの指定はさらに少なくなりますが、通常はC実装が対象とするハードウェアアーキテクチャに依存します。最も一般的なポインタサイズは、4バイト(32ビットアーキテクチャで一般的)と8バイト(64ビットアーキテクチャで一般的)です。

これは何ですか (**)?

提示するコンテキストでは、それは型指定子_char **_の一部であり、charへのポインターへのポインターを記述しました。

どんな用途がありますか?

多かれ少なかれ、他のデータ型へのポインタとして使用されます。他の型の値に間接的にアクセスする必要がある場合と同様に、ポインター値に間接的にアクセスする必要がある場合があります。また、ポインターの配列(の最初の要素)を指すのに便利です。これは、C main()関数の2番目のパラメーターで使用される方法です。

この特定の場合、ポイント先配列自体の各_char *_は、プログラムのコマンドライン引数の1つを指します。

それはメモリでどのように表されますか?

Cは指定しませんが、通常、ポインターへのポインターは、他のタイプの値へのポインターと同じ表現を持ちます。それが指す値は、単なるポインタ値です。

4
John Bollinger

**は、名前を知っているポインタへのポインタを表します。各質問について説明します。

これは何ですか (**)?

ポインターへのポインター。時々、ダブルポインターを呼び出します。例えば:

int a = 3;
int* b = &a; // b is pointer. stored address of a
int**b = &b;  // c is pointer to pointer. stored address of b
int***d = &c; // d is pointer to pointer to pointer. stored address of d. You get it. 

それはメモリでどのように表されますか?

上の例のcは単なる通常の変数であり、他の変数(ポインター、int ...)と同じ表現を持っています。変数cのメモリサイズはbと同じで、プラットフォームに依存します。たとえば、32ビットコンピューターでは、各可変アドレスに32ビットが含まれるため、サイズは4バイト(8x4 = 32ビット)になります。64ビットコンピューターでは、各可変アドレスは64ビットになり、サイズが8バイト(8x8 = 64ビット)になります。

どんな用途がありますか?

ポインターへのポインターには、状況に応じて多くの使用法があります。たとえば、アルゴリズムクラスで学んだ1つの例を次に示します。リンクされたリストがあります。ここで、リンクリストを変更するメソッドを作成すると、メソッドがリンクリストの先頭を変更する場合があります。 (例:値が5の要素を1つ削除し、先頭の要素を削除し、スワップ、...)。したがって、次の2つのケースがあります。

1。 head要素のポインタを渡すだけの場合。たぶんそのhead要素は削除され、このポインタはもう有効ではありません。

2。ヘッド要素のポインターのポインターを渡す場合。ヘッド要素が削除された場合、ポインターのポインターはまだそこにあるので問題はありません。別のヘッドノードの値を変更するだけです。

上記の例については、ここで参照できます。 リンクリスト内のポインターへのポインター

別の使用法は、2次元配列での使用です。 CはJavaとは異なります。 Cの2次元配列、実際には単なる連続したメモリブロック。 Javaの2次元配列はマルチメモリブロックです(マトリックスの行に依存)

このヘルプを願っています:)

4
hqt

**は、ポインターへのポインターを表します。参照でパラメータを渡したい場合は*を使用しますが、ポインタ自体を参照で渡したい場合は、ポインタへのポインタが必要です。したがって**

3
John Sensebe

誰もが素晴らしい仕事をしたのと同様に、ここに自分の答えを追加するつもりだと思いますが、ポインターへのポインターのポイントが何であるか本当に混乱しました。これを思いついた理由は、ポインターを除くすべての値は値によって渡され、ポインターは参照によって渡されるという印象を受けていたためです。以下を参照してください。

void f(int *x){
    printf("x: %d\n", *x);
    (*x)++;
}

void main(){
   int x = 5;
   int *px = &x;
   f(px);
   printf("x: %d",x);
}

生成するもの:

x: 5
x: 6

これにより、(何らかの理由で)ポインタを渡し、操作してから新しい値を分割して出力するときに、ポインタが参照によって渡されると思いました。関数内でポインターを操作できる場合...ポインターを操作するために、ポインターへのポインターがあるのはなぜですか?

これは私には間違っているように見えましたが、関数内で既にポインターを操作できるのに、ポインターを操作するポインターを持っているのはばかげているからです。しかし、Cのあるもの。 すべてが値で渡されます、ポインターであってもです。アドレスの代わりにいくつかの疑似値を使用してさらに説明します。

//this generates a new pointer to point to the address so lets give the
//new pointer the address 0061FF28, which has the value 0061FF1C.
void f(int 0061FF1C){
    // this prints out the value stored at 0061FF1C which is 5
    printf("x: %d\n", 5);
    // this FIRST gets the value stored at 0061FF1C which is 5
    // then increments it so thus 6 is now stored at 0061FF1C
    (5)++;
}

void main(){
   int x = 5;

   // this is an assumed address for x
   int *px = 0061FF1C;

   /*so far px is a pointer with the address lets say 0061FF24 which holds
    *the value 0061FF1C, when passing px to f we are passing by value...
    *thus 0061FF1C is passed in (NOT THE POINTER BUT THE VALUE IT HOLDS!)
    */

   f(px);

   /*this prints out the value stored at the address of x (0061FF1C) 
    *which is now 6
    */
   printf("x: %d",6);
}

ポインタからポインタへの私の主な誤解は、値渡しと参照渡しです。元のポインターは関数にまったく渡されなかったため、新しいポインターのアドレスのみを指すアドレスを変更することはできません(古いポインターがあったアドレスを指すように古いポインターであるという錯覚を持っています)を指しています!)。

3
James

ポインターへのポインターです。ポインターへのポインターを使用する理由を尋ねる場合、さまざまな良い方法でそれに答える同様のスレッドがあります。

なぜダブルポインターを使用するのか、またはポインターをポインターに使用するのはなぜですか?

3
tramstheman

char **argv なので char** argv。今、char*は基本的にcharの配列なので、(char*)*は、charの配列の配列です。

他の(ゆるい)言葉では、argvは文字列の配列です。この特定の例:呼び出し

myExe dummyArg1 dummyArg2

コンソールではargvを次のようにします

argv[0] = "myExe"
argv[1] = "dummyArg1"
argv[2] = "dummyArg2"
1
Quang Hoang

たとえば、**はポインターへのポインターです。 char **argvchar *argv[]と同じであり、これはchar argv[][]と同じです。それは行列です。

たとえば、4行の行列を宣言できますが、JaggedArraysのように列数が異なります。

マトリックスとして表されます。

ここ メモリに表現があります。

1
Simply Me