ポインタ演算

C言語にはポインタ演算と呼ばれる、データ型の幅を基準として、アドレスの位置を進めることができる機能があります。

これは、実務では配列の要素の位置をひとつづつ、進めていくために使われることが多いので、まずポインタ配列の関係について書きます。

C言語での配列とポインタの関係性

C言語では、配列の要素へのアクセスは、ポインタ演算のシンタックスシュガーです。calloc関数で割り当てたメモリ領域に配列としてアクセスできることをまず感じてみてください。

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
  
  // ポインタを使って配列を表現
  int32_t nums_length = 10;
  int32_t* nums = calloc(sizeof(int32_t), nums_length);
  
  // 配列の要素に代入
  nums[2] = 5;
  
  // 配列の要素を出力
  printf("nums[2]: %d\n", nums[2]);
  
  // ポインタ演算を使って要素の値を設定
  *(nums + 2) = 7;

  // 配列の要素を出力
  printf("nums[2]: %d\n", nums[2]);
  
  // ポインタ演算を使って要素の値を出力
  printf("*(num2 + 3): %d\n", *(nums + 2));
  
  free(nums);
}

出力結果。

nums[2]: 5
nums[2]: 7
*(nums + 2): 7

「nums[2]」が「*(nums + 2)」のシンタックスシュガーになっていることを、見てください。

numsはcallocで割り当てられたメモリの先頭のアドレスを表現しています。

「*(nums + 2)」で、実体を取得しています。実体の取得については、ポインタで解説しています。

ポインタ演算を見てみる

ポインタ演算は、sizeof演算子で取得できるサイズを単位とした値を、指定した値にかけて、アドレスを進めます。

「nums + 2」という演算は、3を足しているように見えますが、num2はint32_t型ですので、実際には「sizeof(int32_t) * 3」増えます。

実際に出力してみましょう。アドレスは「%p」フォーマット指定子で、出力できます。16進数で出力されます。

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
  
  // ポインタを使って配列を表現
  int32_t nums_length = 10;
  int32_t* nums = calloc(sizeof(int32_t), nums_length);
  
  printf("nums: %p\n", nums);
  printf("nums + 1: %p\n", nums + 1);
  printf("nums + 2: %p\n", nums + 2);
  printf("nums + 3: %p\n", nums + 3);
  
  free(nums);
}

僕の環境での出力結果です。sizeof演算子で得られるint32_tのサイズは4ですので、これを単位として、アドレスが増えます。

nums: 0x4538010
nums + 1: 0x4538014
nums + 2: 0x4538018
nums + 3: 0x453801c

違いを見るためにint64_tの場合も試しておきます。

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
  
  // ポインタを使って配列を表現
  int64_t nums_length = 10;
  int64_t* nums = calloc(sizeof(int64_t), nums_length);
  
  printf("nums: %p", nums);
  printf("nums + 1: %p, nums + 1);
  printf("nums + 2: %p, nums + 2);
  printf("nums + 3: %p, nums + 3);
  
  free(nums);
}

出力結果です。sizeof演算子で得られるint64_tのサイズは8ですので、これを単位として、アドレスが増えます。

nums: 0x6c39010
nums + 1: 0x6c39018
nums + 2: 0x6c39020
nums + 3: 0x6c39028

配列としてアクセスできる場合に、ポインタ演算を使う理由はありますか?

えーと、あまりないです。配列の方が直感的で、可読性が高いと思います。

ポインタ演算のサンプル

よく使う場合のサンプルとして、文字列に含まれる文字に順番にアクセスするというものを書いておきます。

C言語の文字列は「\0」で終わるので、ポインタで、文字位置を指して、順番に処理していくということを時々書きます。

#include <stdint.h>
#include <stdio.h>

int main(void) {
  
  // 文字列
  const char* message = "Hello World!";
  
  // 文字列の先頭のアドレスを取得
  const char* ch_ptr = message;
  
  // ポインタの指している値が、\0になるまで繰り返す
  while (*ch_ptr != '\0') {
    
    // 1文字出力
    printf("%c", *ch_ptr);
    
    // ポインタを進める
    ch_ptr++;
  }
  printf("\n");
}

出力結果です。

Hello World!

関連情報