int n = 50;
普段何気なく変数を定義してプログラムを書いているが、それらの変数や値がどのようにメモリに保存されていかを意識したことはあるだろうか?少なくとも自分はなかった、、、
ポインタ
C言語には保存する値がメモリのどこのアドレスに保存されているか確認する方法がある
int n = 50;
int *p = &n;
- &(変数):変数が保存されているアドレスを取得する表記方法
- *(変数名):アドレスを格納するための変数の定義方法
実際に変数のアドレスを出力したコード例
#include <stdio.h>
int main(void){
int n = 50;
int *p = &n;
printf("%p\n", p);
}
出力結果:0x7ffcb4578e5c(人それぞれの値)
そして*にはもう1つの役割として、ポインタ内のアドレス先の値を参照するというものがある。(講義では郵便ポストで例えられている。引っ越した後に、以前のポストには引越し先の住所を同封することで、引越し先の値(住所)を示すことになる。)
#include <stdio.h>
int main(void){
int n = 50;
int *p = &n;
printf("%p\n", p);
printf("%i\n", *p);
}
出力結果
0x7ffcb4578e5c(人それぞれの値)
50
文字列について
ではStringではどうポインタが活用できるだろうか??以前の講義では、Stringはcharの配列であると説明した
string s = "HI!";
以上のように文字列型の変数を定義するとメモリ上では H I ! \0 の4つが保存される。
→文字列の終わりは\0でわかっているので、文字列の最初を示せば良い
→つまり変数sは"H"が格納されているアドレスを示せれば良いということだ
string = char *
と言い換えられる
#include <stdio.h>
#include <cs50.h>
int main(void){
string s = "HI!";
char *p = s[0];
printf("%p\n", p);
printf("%p\n", s);
}
出力結果
0x402004
0x402004
printf()にて文字列の値を表示するということは、変数の最初のアドレスへアクセスしnullまでのものを表示することである。
変数同士の比較
Stringの変数がどのように示されているかわかったところで、変数同士の比較について考えていこう
#include <stdio.h>
#include <cs50.h>
int main(void){
int i = get_int("i: ");
int j = get_int("j: ");
if (i == j){
printf("Same\n");
} else {
printf("Different\n");
}
}
コンソール
1回目
i: 50
j: 50
2回目
i: 50
j: 42
まずint同士での比較をしていく。上記のコードを実行して、コンソールで1・2回目の値を入れると何が出力されるだろうか??
1回目
i: 50
j: 50
Same
2回目
i: 50
j: 42
Different
特になんの疑問もなくこうなるだろう。では次に文字列で比較してみよう。
#include <stdio.h>
#include <cs50.h>
int main(void){
string i = get_string("i: ");
string j = get_string("j: ");
if (i == j){
printf("Same\n");
} else {
printf("Different\n");
}
}
コンソール
1回目
i: HI!
j: BYE!
2回目
i: HI!
j: HI!
int同様にstringで実行するとどうなるだろうか??
1回目
i: HI!
j: BYE!
Different
2回目
i: HI!
j: HI!
Different
こうなるのだ。1回目は理解できるが2回目はなぜDifferentになってしまうのだろう??
変数の定義部分を以下のようにすると、わかりやすくなるかもしれない
string i = get_string("i: ");
string j = get_string("j: ");
↓
char *i = get_string("i: ");
char *j = get_string("j: ");
stringは文字の配列であり、その変数には値そのものは入っていない。保存されているのは 1文字目のアドレス である。
→したがって、if (i == j)は変数iと変数jのアドレス同士を比較していることになる。
→値は同じでもそれぞれで定義されているので、アドレスは異なるため一致しなかったのだ
では文字列同士を比較するにはどうしたらいいだろう?
#include <stdio.h>
#include <cs50.h>
#include <string.h>
int main(void){
string i = get_string("i: ");
string j = get_string("j: ");
if (strcmp(i, j) == 0){
printf("Same\n");
} else {
printf("Different\n");
}
}
strcmp関数を使用すれば比較できる。この関数内ではforかwhileのループで文字を1つ1つ同じか確認して、全て正しければ0を返すようになっている
文字列のコピー
文字列を別の変数にコピーするための、以下のコードを実行してみてほしい。
#include <stdio.h>
#include <cs50.h>
#include <string.h>
int main(void){
string s = get_string("s: ");
string t = s;
t[0] = toupper(t[0]);
printf("s: %s\n", s);
printf("s: %s\n", t);
}
コンソール
s: hi!
一見良さそうなコードではあるが、この出力結果はというと、、、、
コンソール
s: hi! (入力値)
s: Hi!
t: Hi!
tだけではなくsの1文字目も大文字になってしまっている。これは比較処理と同様に、文字列の変数には1文字目のアドレスが保存されていることが起因している。
string t = s;
この代入によって、sとtが共に同じアドレスを保持してしまった。その結果tが変化するとその影響がsにも起きてしまう。
→ではどのように値だけのコピーを行うのか?
strcpy()という変数を用いる
→しかしこれには、コピー先の変数とコピー対象の変数の2つを引数に取る必要がある。
→現状コピー対象の変数しかない
→コピー先の変数を初期化する必要がある(・ω・`)ドウスレバイインダ?
動的メモリ割り当て
上記にあったように、変数に値はいらないけどメモリ領域だけ確保して定義したい場合がある。その時に活用できる関数がある
malloc(確保したいメモリのバイト数):引数に数値をとり、その数値分の連続したメモリ領域を確保して最初のアドレスを返す
free(変数名):確保したメモリ領域を解放して、他の用途に使える状態にする
これを利用して、文字列のコピー処理を修正してみる
#include <stdio.h>
#include <ctype.h>
#include <cs50.h>
#include <stdlib.h>
#include <string.h>
int main(void){
string s = get_string("s: ");
// ここでメモリ領域を確保して変数の初期化を行う
// sの文字数+1文字分のメモリを確保→文字列が終了する\0分のメモリを確保
string t = malloc(strlen(s) + 1);
if(t == null){
// mallocでうまくメモリ領域を取得できなかった場合エラーを返す
return 1;
}
strcpy(t, s);
if (strlen(t) > 0){
// sの入力で文字を入れずEnterだけだった場合は行わない
t[0] = toupper(t[0]);
}
printf("s: %s\n", s);
printf("s: %s\n", t);
free(t);
return 0;
}
コンソール
s: hi! (入力値)
s: hi!
t: Hi!
このように、文字列のコピーを行ってもtとsで別々に扱うことができるようになった
メモリ管理に関するエラー
バッファオーバーフロ-
確保したメモリ領域を超えて、書き込みや読み込みを行うこと。それによってデータの消失やセグメンテーションフォルトが起きる。
メモリリーク
使用したメモリ領域を解放せずに、そのまま残してしまうこと。
コード例
#include <stdio.h>
int main(void){
int *x = malloc(3 * sizeof(int));
x[1] = 72;
x[2] = 75;
x[3] = 80;
}
この処理をコンソールで実施してみても、特に何も起きない
→そこまで大きなエラーではないため
→コンソールにメモリ関連のバグを見つけるためのコマンドを実行してみる
valgring ./実行したいファイル名
出力結果
~~
==2890== Invalid write of size 4
==2890== at 0xxxxxx: main (memory.c:8)
~~
==2890== 12 bytes in 1 blocks are definitely lost in loss record 1 of 1
~~
出力結果には2つの問題が隠されている
- Invalid write of size 4
size 4における無効な書き込みがあることを意味している
→これは本来配列のインデックスは0から始まるのに1から始めてしまった
→x[3]というmallocでは定義されていないメモリに書き込みを行ったことを意味する
→バッファオーバーフロー - 12 bytes in 1 blocks are definitely lost in loss record 1 of 1
12 byteのブロックが正しく解放されていないことを意味している
→mallocでメモリ領域を確保し、処理に使用した後は必ず解放する必要がある
→freeを最後に書く
→メモリリーク
コードを修正すると
#include <stdio.h>
int main(void){
int *x = malloc(3 * sizeof(int));
x[0] = 72;
x[1] = 75;
x[2] = 80;
free(x);
}
セグメンテーションフォルト
不正なメモリへのアクセスを行ってしまうこと
以下のコードを実行して、コンソールで入力を行う
#include <stdio.h>
#include <stdlib.h>
int main(void){
char s[4];
printf("s: ");
scanf("%s", s);
printf("s: %s\n", s);
}
コンソール
s: heloooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
出力結果は
k
s: heloooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(入力値)
s: heloooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
Segmentation fault
セグメンテーションフォルトが起きました
→これはchar s[4]で4文字分のメモリ領域に対して、入力値が明らかに4文字以上だったため、アクセスしてはいけない領域にアクセスしてしまったためである。
視聴元