• 検索結果がありません。

第1回 プログラミング演習3 センサーアプリケーション

N/A
N/A
Protected

Academic year: 2021

シェア "第1回 プログラミング演習3 センサーアプリケーション"

Copied!
42
0
0

読み込み中.... (全文を見る)

全文

(1)

C プログラミング

ポインタなんて恐くない!

-藤田 悟

(2)

目標

• C 言語プログラムとメモリ、ポインタの関係を

深く理解する。

– C言語プログラムは、メモリを素のまま利用できま

す。これが原因のエラーが多く発生します。

– メモリマップをよく頭にいれて、ポインタの動きを

理解できれば、C言語もこわくありません。

(3)
(4)

ディレクトリの作成と移動

• mkdir chapter1

• cd chapter1

(5)

前提知識: printf

• C言語のコンソール出力の基本形

– printf(“書式”, 変数1, 変数2, …);

– 書式には、表示したい文字列と、書式文字列 (%d な

ど)を書く。

– 書式制御文字列と同じ数だけの変数を後ろに続ける。

• %d : int の表示

• %ld : long の表示

• %f : float, double の表示

• %x : 整数の16進数表示

• %lx: long 整数の16進数表示

• %s : 文字列表示

• %c : 文字表示

• ¥n : 改行

(6)

prog0.c

#include <stdio.h> // 入出力を使うときに宣言

// argc は引数の数。argv は引数の文字列の配列

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

int x = 10;

printf(“%d¥n”, x);

}

コンパイル: gcc prog0.c –o prog0

実行: ./prog0

(7)

変数の大きさ、変数のアドレス

• 変数を格納するためには、メモリが必要

– メモリには、1バイト単位に「アドレス(番地)」がある。

– int x; という変数に対して、&x と指定すると、変数 x

のアドレス(ポインタアドレス)がわかる。

– アドレスを画面に表示するには、

• printf(“%lx¥n”, &x); // %lx はlongの16進数表示

• 変数を格納するためには、メモリが何バイト必要

– 変数が何バイトか知るには、

sizeof x

を用いる。

(8)

prog1.c

• int の変数 x, y の値と、アドレスと、大きさをまとめて表示

する。

#include <stdio.h>

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

int x = 112;

int y = 115;

// sizeof x は、 sizeof(x)のように書いても良い

printf("%d %lx %d¥n", x, &x, sizeof x);

printf("%d %lx %d¥n", y, &y, sizeof y);

}

(9)

演習1: メモリマップを完成させよう!

• prog1.c を実行して、x や y が、何番地のメモ

リに格納されていたかを確認して、メモリマッ

プを作成せよ

7fff3c4f92ac

7fff3c4f92ad

7fff3c4f92ae

7fff3c4f92af

7fff3c4f92a8

7fff3c4f92a9

7fff3c4f92aa

7fff3c4f92ab

x と y は、どちらが小さいアドレスにある?

x と y のサイズとメモリは一致しているか?

PCが4Gバイトのメモリを持っているとすると、

4G = 0x100000000 (16進数で、0が8個)

表示は7fff から始まっているけれど、本当は

そんなに大きなメモリはない

0

7fffffffffff

(10)

4バイト境界のメモリマップ

• 1バイトずつだと、縦に長くなりすぎるので、4

バイトずつ横に並べて、メモリマップを書く

7fff3c4f92ac

7fff3c4f92a8

0

7ffffffffffc

00

00

00

00

00

00

00

00

変数 x

変数 y

なぜか、上下が逆!!

(11)

演習2: prog2.c

• int x; に加えて、long y; float f; double d; char

c; を宣言して、値、メモリアドレス、サイズを表

示して、メモリマップを作成せよ。

– char c の値は、int で表示してよい (%dを使う)

– x, y, f, d, c は、どの順番に格納されるのか

– ゴミの値が入っていないか?

• ゴミとは、初期値が0でない変な値

(12)

prog2.c

#include <stdio.h>

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

int x;

long y;

float f;

double d;

char c;

printf("%d %lx %d¥n", x, &x, sizeof(x));

printf("%ld %lx %d¥n", y, &y, sizeof(y));

printf("%f %lx %d¥n", f, &f, sizeof(f));

printf("%f %lx %d¥n", d, &d, sizeof(d));

printf("%d %lx %d¥n", c, &c, sizeof(c));

}

7fff3c4f92ac

7fff3c4f92a8

0

7ffffffffffc

7fff3c4f92a4

7fff3c4f92a0

7fff3c4f929c

7fff3c4f9298

7fff3c4f9294

7fff3c4f9290

7fff3c4f928c

変ですねぇ…

x

y

f

d

c

(13)

配列のメモリを見る

• 配列の宣言は、int array[4]; のように行う

• 配列要素のメモリアドレスは、&array[0],

&array[1]のようにして、得られる

• 配列の先頭アドレスは、&array でわかる

• 配列の全体のサイズは、 sizeof array

• 配列の一要素のサイズは、sizeof array[0]

(14)

prog3.c

#include <stdio.h>

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

int x;

int array[4];

printf("%d %lx %d¥n", x, &x, sizeof(x));

printf("%d %lx %d¥n", array[0], &array[0], sizeof array[0]);

printf("%d %lx %d¥n", array[1], &array[1], sizeof array[1]);

printf("%d %lx %d¥n", array[2], &array[2], sizeof array[2]);

printf("%d %lx %d¥n", array[3], &array[3], sizeof array[3]);

// & の位置や、sizeof の位置に注意!! 書式の %lx にも注意!!

printf("%lx %lx %d %d¥n", array, &array, sizeof array, sizeof &array);

}

(15)

ポインタ変数

• アドレスを格納するための変数をポインタ変数と

呼ぶ。

– int *xp; // 整数へのポインタを格納する変数

• ポインタ変数へのアドレスの代入には、元の型

の変数のアドレスを代入する

– int *xp;

– int x;

– xp = &x; // xのアドレスをxpに代入

• 演習4: ポインタ変数の sizeof と、上記のプログラ

ムで格納されたポインタ変数の値を確認し、メモ

リマップを作成せよ。

(16)

演習4 prog4.c

#include <stdio.h>

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

int x;

int *xp;

xp = &x;

x = 3;

printf("x = %d %lx¥n", x, &x);

printf(“xp = %lx %lx¥n”, xp, &xp); // &xp は変数xpのアドレス

}

(17)

ポインタ変数を使った値の書き換え

• int x = 3;

• int *xp;

• xp = &x;

• という状況で、xp は x の変数のアドレスが

入っている。

• ここで、*xp = 5; と実行すると、xp が指し示す

アドレスの先のデータが5に書き換わる

– すなわち、x の値が書き換わる

(18)

prog5.c

#include <stdio.h>

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

int x = 3;

int *xp;

xp = &x;

printf("x = %d %lx¥n", x, &x);

printf(“xp = %lx %lx¥n", xp, &xp);

*xp = 5;

printf("x = %d %lx¥n", x, &x);

printf(“xp = %lx %lx¥n", xp, &xp);

}

演習5: メモリマップを作成せよ

(19)

配列要素のアドレスをポインタ変数に

格納

• 配列要素のアドレスは、 &array[0] のようにし

て得られる。

• ポインタ変数の宣言は、int *xp;

• したがって、

– xp = &array[0];

– のように書くと、配列の第0要素のアドレスが、xp

に代入される

• *xp = 12; のように書いて、配列の中身をすべ

て12に書き換え、メモリマップで確認しよう。

(20)

演習6: prog6.c

#include <stdio.h>

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

int array[4];

int *xp;

printf("%lx %lx¥n", xp, &xp);

int i;

for(i = 0; i < 4; i++) {

xp = &array[i];

printf("BEFORE: %d %lx %d¥n", array[i], &array[i], sizeof array[i]);

*xp = 12;

printf("AFTER: %d %lx %d¥n", array[i], &array[i], sizeof array[i]);

}

(21)

ポインタ変数のインクリメント

• ポインタ変数に1を足すと、ポインタの指し示

す「型」の大きさ分だけアドレスが足される

– int *xp; の時、xp++ はアドレスに 4 を足す

– long *xp; の時、xp++はアドレスに 8を足す

• ポインタ変数を加算しながら、指し示すアドレ

スの内容を書き換えてみよう

– *xp = 12;

– xp++;

– まとめて書くと、*xp++ = 12; と書くことができる

(22)

演習7: prog7.c

#include <stdio.h>

int main(int argc, char *argv[]) { int array[4]; int *xp; printf("%lx %lx¥n", xp, &xp); int i; xp = &array[0]; for(i = 0; i < 4; i++) { printf("BEFORE: %d %lx¥n", array[i], xp); *xp = 12; xp++; printf("AFTER: %d %lx¥n", array[i], xp); } }

(23)

文字列は char の配列

• C言語には、Java のような String 型はなく、文

字(char: 8bits=1byte)の配列として扱う

• 文字列の長さもわからないので、char の値に

0 が来たら、文字列の終了とみなす。

char univ[] = “hosei”;

h

o

s

e

i ¥0

univ

(24)

演習8: prog8.c

#include <stdio.h>

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

char univ[] = "hosei";

int i = 0;

char *cp;

cp = univ;

printf("%c %d %x %lx %lx¥n", *cp, *cp, *cp, cp, &cp);

for(i = 0; i < 6; i++) {

printf("%c %d %x %lx¥n", *cp, *cp, *cp, cp);

cp++;

}

}

(25)

整数はどのように格納される?

• int *xp の値や、x の値は、アドレスにどのよう

な順番で格納されているのだろうか

• これを確認するためには、1バイトずつデータ

を見なければならない

• 1バイトのデータは、char 型です。

– 特に、符号なしの1バイトとしたい場合、unsigned

char と書きます。

(26)

演習9: prog9.c

#include <stdio.h>

int main(int argc, char *argv[]) { int x = 0x12345678;

long lx = 0x123456789abcdef0; unsigned char *cp;

printf("%lx¥n", &cp); int i = 0;

cp = (unsigned char *)&x; for(i = 0; i < 4; i++) {

printf("%x %lx¥n", *cp, cp); cp++;

}

cp = (unsigned char *)&lx; for(i = 0; i < 8; i++) {

printf("%x %lx¥n", *cp, cp); cp++;

} }

(27)

演習10: prog10.c

下記のプログラムを実行して、何が起こっているかを、メモリ

マップに示して説明せよ。

#include <stdio.h>

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

long lx = 0x6965736f68;

char *cp;

cp = (char *)&lx;

printf("%s¥n", cp);

}

(28)

関数呼び出し

• 関数呼び出しをしたとき、変数はどのようにメモ

リにマッピングされているか

• 特に、再帰呼び出しした時にメモリマップはどう

なるのか

• 関数から戻ってきたときに、関数内で使っていた

ローカル変数はどうなるのか

– 別の関数が呼び出された時のローカル変数領域に

なり、上書きされる

– ローカル変数領域へのポインタを、呼び出し元の関

数に引き渡すと、大きなエラーになる

(29)

演習11: prog11.c

#include <stdio.h> int fact(int x) {

int result;

printf("x = %d %lx¥n", x, &x);

printf("result = %d %lx¥n", result, &result); if(x == 0) { result = 1; } else { result = x * fact(x - 1); } return result; }

int main(int argc, char **argv) { int x; x = 2; printf("x = %d %lx¥n", x, &x); int y = fact(x); printf("y = %d %lx¥n", y, &y); printf("%d¥n", y); }

(30)

グローバル変数、malloc()

• 関数内のローカル変数ではなく、関数の外に

定義するグローバル変数、malloc() で動的に

確保されるメモリは、どこに確保されるのであ

(31)

演習12: prog12.c

#include <stdio.h>

#include <stdlib.h> // malloc を使うときに必要 int ex;

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

char *cp = "hosei"; char cparray[] = "hosei"; char *mp = (char *)malloc(8); printf("x = %d %lx¥n", x, &x); printf("ex = %d %lx¥n", ex, &ex); printf("cp = %lx %lx¥n", cp, &cp);

printf("cparray = %lx %lx¥n", cparray, &cparray); printf("mp = %lx %lx¥n", mp, &mp);

(32)
(33)

C言語はメモリが命

• ポインタはメモリのアドレス(番地)である

• int は 4バイト、long は8バイトで、変数宣言する

と、適当に隙間も作りながらメモリが割り当てら

れる

• ポインタ変数は、64bitsOSの時、64bits(8バイト)

– ポインタ変数を+1すると、変数の型に合わせて、数バ

イトずつアドレスが増加する

• ローカル変数は、関数呼び出しごとにメモリが割

り当てられる

– ローカル変数で配列を確保すると大変なことに…

(34)
(35)

• ディレクトリを変更します。

• cd ..

• mkdir chapter2

• cd chapter2

(36)

2次元配列

• unsigned char array[3][3]; と宣言したら、どのよう

なメモリが割り当てられるのか

– array の値と sizeof は?

– array[0] の値と sizeof は?

– array[0][0] の値と sizeof は?

– &array の値はと sizeof は?

– &array[0] の値と sizeof は?

– &array[0][0] の値と sizeof は?

– &array[0][1]の値は?

– &array[1][0]の値は?

– &array[1]の値は?

(37)

演習1: prog1.c

#include <stdio.h>

int main(int argc, char *argv[]) { unsigned char array[3][3]; int i;

int j;

for(i = 0; i < 3; i++) { for(j = 0; j < 3; j++) {

printf("%d %lx¥n", array[i][j], &array[i][j]); }

}

printf("array = %lx %d¥n", array, sizeof array);

printf("array[0] = %lx %d¥n", array[0], sizeof array[0]);

printf("array[0][0] = %lx %d¥n", array[0][0], sizeof array[0][0]); printf("&array = %lx %d¥n", &array, sizeof &array);

printf("&array[0] = %lx %d¥n", &array[0], sizeof &array[0]);

printf("&array[0][0] = %lx %d¥n", &array[0][0], sizeof &array[0][0]); printf("&array[0][1] = %lx %d¥n", &array[0][1], sizeof &array[0][1]); printf("&array[1][0] = %lx %d¥n", &array[1][0], sizeof &array[1][0]); printf("&array[1] = %lx %d¥n", &array[1], sizeof &array[1]);

(38)

演習2: prog2.c

#include <stdio.h>

int main(int argc, char *argv[]) { unsigned char array[3][3];

unsigned char *cp; // char 配列をアクセスし、1バイトごとに増えるポインタ編巣 unsigned char (*cpp)[3]; // char配列3個分ずつ増えるポインタ変数

unsigned char (*cppp)[3][3]; // char 配列3*3個分ずつ増えるポインタ変数 cp = array[0]; printf("cp = %lx¥n", cp); cp++; printf("cp = %lx¥n", cp); cpp = array; printf("cpp = %lx¥n", cpp); cpp++; printf("cpp = %lx¥n", cpp); cppp = &array; printf("cppp = %lx¥n", cppp); cppp++; printf("cppp = %lx¥n", cppp); }

(39)

配列とポインタ

• prog3.c で何が起きているのか

• 演習3: プログラムを実行し、メモリマップを作

成した上で、動作を説明せよ。

(40)

prog3.c

#include <stdio.h>

int main(int argc, char *argv[]) { unsigned char array[3][3]; unsigned char *cp; unsigned char (*cpp)[3]; unsigned char (*cppp)[3][3]; cp = array[0]; cp++; *cp = 'h'; cp[1] = 'o'; cpp = array; cpp++; *cpp[0] = 's'; cpp[0][2] = 'e'; cppp = &array; (*cppp)[2][2] = 'i'; int i, j; for(i = 0; i < 3; i++) { for(j = 0; j < 3; j++) {

printf("%c %lx¥n", array[i][j], &array[i][j]); }

} }

(41)

演習4

• 30バイトの unsigned char の配列を calloc() で

確保し、0を含む3の倍数番目の要素を 0xff に

書き換えよ。

– #include <stdlib.h>

– unsigned char *cp = (unsigned char *)calloc(30, 1);

– 3の倍数番目の要素に、どのようにして、値をセッ

(42)

演習5

• 30バイトの unsigned char の配列を calloc() で確

保し、

– 0を含む3の倍数番目の要素を 1

– 3の倍数+1番目の要素を2

– 3の倍数+2番目の要素を3

にセットせよ。

• 別途、10バイトの unsigned char の配列を calloc()

で用意し、上記の30バイトの配列から、3バイトず

つ加算をした結果を格納せよ。

参照

関連したドキュメント

例えば、総トン数 499 トン・積載トン数 1600 トン主機関 1471kW(2000PS)の内航貨 物船では、燃料油の加熱に使用される電力は

各テーマ領域ではすべての変数につきできるだけ連続変量に表現してある。そのため

「特殊用塩特定販売業者」となった者は、税関長に対し、塩の種類別の受入数量、販売数

いてもらう権利﹂に関するものである︒また︑多数意見は本件の争点を歪曲した︒というのは︑第一に︑多数意見は

られる。デブリ粒子径に係る係数は,ベースケースでは MAAP 推奨範囲( ~ )の うちおよそ中間となる