cの文字列の配列から個々の文字にアクセスするにはどうすればよいですか?

StackOverflow https://stackoverflow.com/questions/606879

  •  03-07-2019
  •  | 
  •  

質問

文字列の配列内の単一の文字をアドレス指定する方法を理解しようとしています。また、これによりもちろん、一般的な添字のポインターへのポインターを理解することができます。 char ** a があり、2番目の文字列の3番目の文字に到達したい場合、これは機能します: **((a + 1)+2)?どうやら...

役に立ちましたか?

解決

ほぼ、しかし完全ではありません。正解は次のとおりです。

*((*(a+1))+2)

最初に実際の文字列ポインターの1つを逆参照する必要があり、次に選択した文字列ポインターを目的の文字まで逆参照する必要があるためです。 (操作の順序を明確にするために、括弧を追加していることに注意してください。)

代わりに、次の式:

a[1][2]

も機能します!...そしておそらくあなたがやろうとしていることの意図がより自明であり、表記自体がより簡潔であるため、おそらく好まれます。この形式は、この言語に慣れていない人にはすぐには明らかではないかもしれませんが、配列表記が機能する理由は、Cでは、配列のインデックス付け操作が実際に同等のポインター操作の略記であるためであることを理解してください。すなわち:*(a + x)はa [x]と同じです。したがって、そのロジックを元の質問に拡張することにより、カスケードされた2つの個別のポインター逆参照操作がカスケードされ、式a [x] [y]は一般形式の*((*(a + x))+ y)。

他のヒント

ポインタを使用する必要はありません。

  

int main(int argc、char ** argv){

     

printf("の3番目の文字   argv [1]は[%c]です。\ n"、argv [1] [2]);

     

}

その後:

  

$ ./main helloの3番目の文字   argv [1]は[l]です。

それは1と1です。

必要に応じてポインターを使用できます...

  

*(argv [1] +2)

または

  

*((*(a + 1))+ 2)

誰かが上で指摘したように。

これは、配列名がポインターであるためです。

Iirc、文字列は実際には文字の配列であるため、これは機能するはずです:

a[1][2]

プログラミングの説明セクションだけで言及する価値のあるポインター、文字列について説明しているJon Ericksonの著書Hacking the art of Exploitation Art第2版に素晴らしいCプログラミングの説明があります/08/hacking-the-art-of-exploitation.pdf。

質問はすでに回答されていますが、詳細を知りたい他の人は、エリクソンの本からの次のハイライトが質問の背後にある構造のいくつかを理解するのに役立つかもしれません。

ヘッダー

おそらく使用する変数操作に使用できるヘッダーファイルの例。

stdio.h-http://www.cplusplus.com/reference/cstdio/

stdlib.h-http://www.cplusplus.com/reference/cstdlib/

string.h-http://www.cplusplus.com/reference/cstring/

limits.h-http://www.cplusplus.com/reference/climits/

機能

おそらく使用する汎用関数の例。

malloc()-http://www.cplusplus.com/reference/cstdlib/malloc/

calloc()-http://www.cplusplus.com/reference/cstdlib/calloc/

strcpy()-http://www.cplusplus.com/reference/cstring/strcpy/

メモリ

" コンパイルされたプログラムのメモリは、テキスト、データ、bss、ヒープ、スタックの5つのセグメントに分割されます。各セグメントは、特定の目的のために確保されているメモリの特別な部分を表します。テキストセグメントは、コードセグメントとも呼ばれます。これは、プログラムの組み立てられた機械語命令が置かれている場所です"。

" このセグメントでの命令の実行は、前述の高度な制御構造と機能のおかげで、非線形です。 分岐にジャンプし、アセンブリ言語の命令を呼び出します。プログラムとして 実行すると、EIPはテキストセグメントの最初の命令に設定されます。の プロセッサは、次に実行ループを実行します。"

" 1。 EIPが指している命令を読み取ります"

" 2。 EIPに命令のバイト長を追加します"

" 3。手順1で読み取った命令を実行します"

" 4。ステップ1に戻ります"

" 場合によっては、命令がジャンプまたは呼び出し命令になります。 EIPをメモリの別のアドレスに変更します。プロセッサは 実行が非線形であると予想しているため、変更に注意してください とにかく。ステップ3でEIPが変更された場合、プロセッサーはステップ1に戻り、EIPが変更されたもののアドレスで見つかった命令を読み取ります。"

" テキストセグメントでは書き込み許可が無効になっています。これは変数の保存には使用されず、コードのみが使用されるためです。これにより、人々が実際にプログラムコードを変更することを防ぎます。このメモリセグメントに書き込もうとすると、プログラムはユーザーに何か悪いことが起こったことを警告し、プログラムは 殺されます。このセグメントが読み取り専用であることのもう1つの利点は、 プログラムの異なるコピー間で共有でき、複数の 問題なくプログラムを同時に実行できます。そうすべき また、このメモリセグメントのサイズは固定されていることに注意してください。 変更点"。

" データおよびbssセグメントは、グローバルおよび静的プログラムを格納するために使用されます 変数。データセグメントには初期化されたグローバル変数と静的変数が入力され、bssセグメントには初期化されていない対応変数が入力されます。これらのセグメントは書き込み可能ですが、サイズも固定されています。機能的なコンテキスト(前の例の変数jなど)にかかわらず、グローバル変数が持続することに注意してください。グローバル変数と静的変数は、独自のメモリセグメントに格納されているため、永続化できます"。

" ヒープセグmentは、プログラマが直接できるメモリのセグメントです コントロール。このセグメントのメモリブロックは、次の目的で割り当てて使用できます。 プログラマが必要とするものは何でも。ヒープに関する重要なポイント セグメントのサイズは固定サイズではないため、必要に応じて大きくしたり小さくしたりできます。"。

" ヒープ内のすべてのメモリはアロケータおよびデアロケータアルゴリズムによって管理されます。これらのアルゴリズムは、それぞれヒープ内のメモリ領域を使用のために予約し、予約を削除して、メモリのその部分を後で予約するために再利用できるようにします。ヒープは、その方法に応じて増減します 多くのメモリが使用のために予約されています。これは、ヒープを使用するプログラマを意味します 割り当て関数は、その場でメモリを予約および解放できます。の成長 ヒープはより高いメモリアドレスに向かって下方に移動します。"。

" スタックセグメントも可変サイズであり、関数呼び出し中にローカル関数変数とコンテキストを保存するための一時的なスクラッチパッドとして使用されます。これが、GDBのバックトレースコマンドの対象です。プログラムが関数を呼び出すと、その関数には渡された変数の独自のセットがあり、関数のコードはテキスト(またはコード)セグメントの別のメモリ位置にあります。関数が呼び出されるとコンテキストとEIPを変更する必要があるため、スタックを使用して、渡されたすべての変数、EIPが関数の終了後に戻る場所、およびその関数で使用されるすべてのローカル変数を記憶します。この情報はすべて、まとめてスタックフレームと呼ばれるスタックにまとめて格納されます。スタックには多くのスタックフレームが含まれています。"。

" 一般的なコンピューターサイエンス用語では、スタックは頻繁に使用される抽象的なデータ構造です。ファーストイン、ラストアウト(FILO)の順序があります 、つまり、スタックに入れられる最初のアイテムは、スタックから最後に出るアイテムです。片方の端に結び目がある紐にビーズを置くと考えてください。他のビーズをすべて取り除くまで、最初のビーズを外すことはできません。アイテムがスタックに配置されると、プッシュと呼ばれ、アイテムがスタックから削除されると、ポップと呼ばれます"。

" 名前が示すように、メモリのスタックセグメントは、実際にはスタックフレームを含むスタックデータ構造です。 ESPレジスタは、スタックの末尾のアドレスを追跡するために使用されます。これは、アイテムがそこにプッシュされたりポップされたりするときに常に変化します。これは非常に動的な動作であるため、スタックも固定サイズではないことは理にかなっています。スタックの変更に伴う、ヒープの動的な増加とは反対 サイズが小さい場合、メモリの視覚的なリストで上に向かって、より低いメモリアドレスに向かって成長します。"。

" スタックのFILOの性質は奇妙に思えるかもしれませんが、スタックが使用されるため コンテキストを保存するには、非常に便利です。関数が呼び出されると、いくつかのことがスタックフレームで一緒にスタックにプッシュされます。 EBPレジスター—フレームポインター(FP)またはローカルベース(LB)ポインターとも呼ばれる —は、現在のスタックフレーム内のローカル関数変数を参照するために使用されます。各スタックフレームには、関数へのパラメーター、そのローカル変数、および物を元に戻すために必要な2つのポインター(保存されたフレームポインター(SFP)と戻りアドレス)が含まれています。の SFPは、EBPを以前の値に復元するために使用され、リターンアドレス 関数呼び出しの後に見つかった次の命令にEIPを復元するために使用されます。これにより、前のスタックの機能コンテキストが復元されます フレーム"。

文字列

" Cでは、配列は特定のデータ型のn個の要素の単なるリストです。 20文字の配列は、メモリに配置された20個の隣接する文字です。配列はバッファとも呼ばれます"。

#include <stdio.h>

int main()
{
    char str_a[20];
    str_a[0] = 'H';
    str_a[1] = 'e';
    str_a[2] = 'l';
    str_a[3] = 'l';
    str_a[4] = 'o';
    str_a[5] = ',';
    str_a[6] = ' ';
    str_a[7] = 'w';
    str_a[8] = 'o';
    str_a[9] = 'r';
    str_a[10] = 'l';
    str_a[11] = 'd';
    str_a[12] = '!';
    str_a[13] = '\n';
    str_a[14] = 0;
    printf(str_a);
}

&quot; 上記のプログラムでは、20要素の文字配列は次のように定義されています str_a、および配列の各要素が1つずつ書き込まれます。数字は1ではなく0から始まることに注意してください。また、最後の文字が0 &quot;であることに注意してください。

&quot; (これはヌルバイトとも呼ばれます。)文字配列が定義されているため、20バイトが割り当てられますが、実際に使用されるのはこれらのバイトのうち12バイトのみです。末尾のNULLバイトプログラミングは、文字列を処理しているすべての関数に操作をすぐに停止するように指示する区切り文字として使用されます。残りの余分なバイトは単なるゴミであり、無視されます。文字配列の5番目の要素にヌルバイトが挿入された場合、printf()関数によって文字Helloのみが印刷されます。&quot;。

&quot; 文字配列の各文字の設定は骨の折れる作業であり、文字列はかなり頻繁に使用されるため、文字列操作用の一連の標準関数が作成されました。たとえば、strcpy()関数は、ソースから宛先に文字列をコピーし、ソース文字列を反復処理し、各バイトを宛先にコピーします(そして、ヌル終了バイトをコピーした後に停止します)&quot;。

&quot; 関数の引数の順序は、最初にIntelアセンブリ構文の宛先、次にソースに似ています。 char_array.cプログラムは、文字列ライブラリを使用して同じことを達成するためにstrcpy()を使用して書き換えることができます。次に示すchar_arrayプログラムの次のバージョンには、string.hが含まれています。これは、文字列関数を使用するためです。&quot;。

#include <stdio.h>
#include <string.h>

int main() 
{
    char str_a[20];
    strcpy(str_a, "Hello, world!\n");
    printf(str_a);
}

C文字列の詳細情報を見つける

http://www.cs.uic.edu/~jbell/CourseNotes/C_Programming/CharacterStrings.html

http://www.tutorialspoint.com/cprogramming/c_strings.htm

ポインター

&quot; EIPレジスタは、&#8220;ポイント&#8221;プログラムの実行中に、そのメモリアドレスを含めることにより、現在の命令に。ポインターの概念は、Cでも使用されます。物理メモリは実際には移動できないため、物理メモリ内の情報をコピーする必要があります。さまざまな機能またはさまざまな場所で使用されるメモリの大きなチャンクをコピーすると、非常に計算コストが高くなる可能性があります。これは、メモリの観点からも高価です。なぜなら、ソースをコピーする前に、新しい宛先コピー用のスペースを保存または割り当てる必要があるからです。ポインターはこの問題の解決策です。大量のメモリブロックをコピーする代わりに、そのメモリブロックの先頭のアドレスを渡す方がはるかに簡単です。&quot;。

&quot; Cのポインターは、他の変数タイプと同様に定義および使用できます。 x86アーキテクチャのメモリは32ビットのアドレス指定を使用するため、ポインターのサイズも32ビット(4バイト)です。ポインターを定義するには、変数名の前にアスタリスク(*)を付けます。そのタイプの変数を定義する代わりに、ポインターはそのタイプのデータを指すものとして定義されます。 pointer.cプログラムは、charデータ型で使用されるポインターの例です。charデータ型はサイズが1バイトのみです&quot;。

#include <stdio.h>
#include <string.h>

int main() 
{
    char str_a[20]; // A 20-element character array
    char *pointer; // A pointer, meant for a character array
    char *pointer2; // And yet another one
    strcpy(str_a, "Hello, world!\n");
    pointer = str_a; // Set the first pointer to the start of the array.
    printf(pointer);
    pointer2 = pointer + 2; // Set the second one 2 bytes further in.
    printf(pointer2); // Print it.
    strcpy(pointer2, "y you guys!\n"); // Copy into that spot.
    printf(pointer); // Print again.
}

&quot; コード内のコメントが示すように、最初のポインターは文字配列の先頭に設定されます。文字配列がこのように参照される場合、実際にはポインターそのものです。これは、このバッファーが以前のprintf()およびstrcpy()関数へのポインターとして渡された方法です。 2番目のポインターは、最初のポインターのアドレスに2を加えた値に設定され、その後、いくつかのものが印刷されます(以下の出力に表示されます)。&quot;。

reader@hacking:~/booksrc $ gcc -o pointer pointer.c
reader@hacking:~/booksrc $ ./pointer
Hello, world!
llo, world!
Hey you guys!
reader@hacking:~/booksrc $

&quot; ポインタにはメモリアドレスが含まれているため、アドレス演算子はポインタとともに使用されることがよくあります。 addressof.cプログラムは、 整数変数のアドレスを入力するために使用されるアドレス演算子 ポインタに。この行は下に太字で示されています&quot;。

#include <stdio.h>

int main() 
{
    int int_var = 5;
    int *int_ptr;
    int_ptr = &int_var; // put the address of int_var into int_ptr
}

&quot; ポインターで使用するために、参照解除演算子と呼ばれる追加の単項演算子が存在します。この演算子は、ポインターが指しているアドレスで見つかったデータを返します

ウィキペディアからの引用記事 Cポインター-

Cでは、配列のインデックス付けは、ポインター演算の観点から正式に定義されています。あれは、 言語仕様では、array [i]が*(array + i)と同等であることが必要です。したがって、Cでは、配列はメモリの連続した領域(ギャップなし)へのポインタと考えることができます。 配列にアクセスするための構文は、逆参照に使用できる構文と同じです ポインター。たとえば、配列は次の方法で宣言および使用できます。

int array[5];      /* Declares 5 contiguous (per Plauger Standard C 1992) integers */
int *ptr = array;  /* Arrays can be used as pointers */
ptr[0] = 1;        /* Pointers can be indexed with array syntax */
*(array + 1) = 2;  /* Arrays can be dereferenced with pointer syntax */

だから、あなたの質問に答えて-はい、ポインタへのポインタは他の種類の宣言なしで配列として使用できます!

a [1] [2] を試してください。または *(*(a + 1)+2)

基本的に、配列参照はポインターの逆参照のための構文糖衣です。 a [2]はa + 2と同じであり、2 [a]と同じです(読みにくいコードが本当に必要な場合)。文字列の配列は、ダブルポインターと同じです。したがって、a [1]または *(a + 1)を使用して2番目の文字列を抽出できます。次に、b [2]または *(b + 2)を使用して、その文字列の3番目の文字(今は「b」と呼びます)を見つけることができます。元の2番目の文字列を「b」に置き換えると、a [1] [2]または *(*(a + 1)+2)になります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top