Edited at
移行Day 4

AdventCalendar Day04 [超Ruby入門]


はじめに

本記事は、超Ruby入門4日目の記事です。

コメント頂ける方は、ガイドラインを読んで頂けると幸いです。


目的

ポインターへの苦手意識をなくす


目標

ポインタを理解する。

ローカル変数とグローバル変数がどのセグメントで管理されるかを知る。


ポインタ

ポインタとはアドレスを取り扱う変数のことだ。

アドレスとはメモリの位置だ。

以下のコードを実行して変数xのアドレスを確認してみよう。


show_address.c

#include<stdio.h>


int main(){
int x = 0;
printf("%p",&x);
return 0;
}

ポインタは*pのように宣言する。以下のコードでポインタの基本的な使い方を確認せよ。


pointer.c

#include<stdio.h>

int main(void){
int x = 1111;
int *p; // ポインタを宣言
p = &x; // xのアドレス代入
printf("x_address: %p\n",&x);
printf("p_address: %p\n",p);
printf("what happens?: %p\n",&p); // ポインタ*pのアドレス
printf("x: %d\n",x);
printf("p: %d\n",*p);
return 0;
}


セグメント

メモリは、三つのセグメントに分けて管理される。

テキストセグメント、データセグメント、スタックセグメントだ。

テキストセグメントは読み出し専用で、プログラムの実行部分が収められている。

データセグメントは、グローバル変数やmallocによって確保したメモリ領域などが収められている。

最後のスタックセグメントには、ローカル変数、引数、関数の戻り値が収められている。


本題

ポインタを説明する時によく使われるものにswap関数がある。

swapとは、二つの変数の値を入れ替える関数のことだ。

さて、以下のコードを実行してみよう。

すると、二つを変数の値が変化していないことが確認できる。


pointer02.c

#include <stdio.h>

#include <stdlib.h>

void swap(int x, int y){
int temp;
printf("swap x_address: %p\n", &x);
temp = x;
x = y;
y = temp;
}

int main(int argc, char *argv[]){
int x, y;

if(argc != 3){
printf("標準入力の引数は2つです。\n");
return 0;
}

x = atoi(argv[1]);
y = atoi(argv[2]);

printf("before_x = %d\n", x);
printf("before_y = %d\n", y);

printf("main x_address: %p\n", &x);
swap(x, y);

printf("after_x = %d\n", x);
printf("after_y = %d\n", y);

return 0;
}


> main x_address: 0x7ffeeb411a6c

> swap x_address: 0x7ffeeb411a3c

プログラム中にxのアドレスを出力しているが、mainとswapのアドレスが異なることがわかるだろう。

変数の要素を変更するためには、同アドレスに対して何かしらの処理を行いたいはずだ。

問題を解決するために、同じにアドレスに対して処理を行う二つの方法を見ていく。


グローバル変数を使う

グローバル変数を宣言し、swap関数の引数をなくすと関数呼び出し後にswapされていることが確認できる。

また、xのアドレスがswap関数内とmain関数内で同じことがわかるだろう。


pointer_global.c


int x, y; // グローバル変数を宣言

void swap(){
int temp;
printf("swap x_address: %p\n", &x);
temp = x;
x = y;
y = temp;
}

int main(int argc, char *argv[]){
~~~~
swap();
~~~~
}


ここで以下のように引数を渡すとどうなるだろうか。

void swap(int x, int y)

swap(x, y);

swap関数内でアドレスが変わっていることが確認できる。

なぜなら、値渡しになりswap関数のスコープで有効なローカル変数が作られたからだ。


参照渡し

二つ目の方法は、参照渡しを利用することだ。

参照渡しは、関数の引数に値ではなくアドレスを渡す。

アドレスのマッピングはswap関数の中であろうと変化しないため、正しく値が入れ替わっているのだ。


pointer03.c


void swap(int *x, int *y){
int temp;
printf("swap x_address: %p\n", &*x);
temp = *x;
*x = *y;
*y = temp;
}

int main(int argc, char *argv[]){
~~~~
printf("main x_address: %p\n", &x);
swap(&x, &y);
~~~~
}



演習


  • 文字列を入れ替えるswap関数を作成せよ。