ミニ補講 Cプログラムのモジュール化

ミニ補講
Cプログラムのモジュール化
2014/6/6
山本・Cuturi研究室 M1
山口 慧
内容
モジュール化とは
 C言語でのモジュール化の方法
ここが重要
 ヘッダファイルの使い方
修飾子extern
修飾子static
インクルードガード
 補足
 宣言と定義について
 コードを分割したときの大域変数について

モジュール化とは
一般に一つの大きなシステムを独立した機能に分割して,
それぞれ別々に設計や管理を行うこと
 例:CPUのレジスタ, ALU, プログラムカウンタ…
 各機能の外部仕様を決めておくことで, 各機能の内部の管
理, 改良, デバッグが行いやすくなる
 例:実験3のHWで外部仕様がわかっていれば, 隣の人に
作ってもらったモジュールも使うことができた(はず)

プログラミングでのモジュール化
ライブラリも機能によって分割されたモジュール
 関数などがどのように実装されているかはわからなくて
も, 仕様(引数や返り値について)を理解していれば使うこ
とができる
 例: C++のmapという連想配列は赤黒木で実装されてい
る(らしい)が, 木の探索法や挿入法を知らなくても使
うことができる


連想配列:int以外をインデックスにできるリストのようなもの
Cでのモジュール化の方法

ヘッダファイル(.h)を使う
 コンパイル時にまとめて書くgcc main.c hello.c
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
Cでのモジュール化の方法

ヘッダファイル(.h)を使う
 構造体の定義や関数のプロトタイプ宣言だけを書く
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
Cでのモジュール化の方法

#includeすると指定したファイルがその場に展開される
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
Cでのモジュール化の方法

関数の中身はソースファイル(.c)に書く
 externは他のファイルに中身が書いてあることを示す
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
Cでのモジュール化の方法

ヘッダで宣言されていないhoge()はmain.cで使えない?
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
Cでのモジュール化の方法

ヘッダで宣言されていないhoge()はmain.cで使えない?
 実はexternをつけて宣言しなくてもgccが勝手に他の
ソースファイルからhoge()を見つけてきてくれる
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
Cでのモジュール化の方法

ヘッダで宣言されていないhoge()はmain.cで使えない?
 実はexternをつけて宣言しなくてもgccが勝手に他の
ソースファイルからhoge()を見つけてきてくれる

ただしintを返す関数だと解釈される
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
Cでのモジュール化の方法

異なるファイルでも同じ名前の関数を定義していると
エラーが出る
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
void hoge(){
……
}
#endif
Cでのモジュール化の方法

staticという修飾子をつけると絶対他のファイルから呼
び出すことはできない
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
static void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
static void hoge(){
……
}
#endif
Cでのモジュール化の方法
#ifndef, #define, #endifはプリプロセッサと呼ばれるもの
 #includeもプリプロセッサ
 コンパイル前にファイルに対して前処理を行う

main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
static void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
Cでのモジュール化の方法
#ifndef, #define, #endifはプリプロセッサと呼ばれるもの
 同じヘッダを二回#includeすることを防止
 同じ型が二回定義されているというエラーを回避

main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
static void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
インクルードガード
#ifndef, #define, #endifはプリプロセッサと呼ばれるもの
 同じヘッダを二回#includeすることを防止
 同じ型が二回定義されているというエラーを回避

hello.h
_HELLO_を定義する
#ifndef _HELLO_
#define _HELLO_
typedef struct name{
char* str;
}name
extern void hello(name);
#endif
もし_HELLO_が定義され
ていなければ#endifまでの
コードを残す
モジュール化のポイント
書き始める前に, 大体どのようなモジュール化ができそう
か考える
 初めから意識していないと, 後から分割するのは面倒
 モジュール(機能)ごとにファイルを分ける
 内部実装が外から見えないようにする
 外から使える関数の取捨選択
 staticをつけた関数は外から使えない

まとめ




プログラムを機能で分割することをモジュール化という
デバッグや改良のしやすさ, 可読性が向上する
ヘッダファイルには構造体の定義や関数のプロトタイプのみ
を書く
 ヘッダファイルにはインクルードガードを書いておく
 関数の宣言にはexternをつけておく
キーワード:モジュール化, ソース分割, ヘッダファイル, プ
リプロセッサ, 実装の隠蔽, など
 これらのキーワードで検索するともっと詳しく説明されて
ます
補足
宣言と定義
C言語において関数(変数)の宣言(declaration)とは, 宣言し
た型と名前を持つ関数(変数)が存在することをコンパイ
ラに教えること
 変数の定義は変数の中身を保存するメモリ領域を確保さ
せること
 関数の定義は実際に行う処理を書いたもの

宣言と定義
C言語において関数(変数)の宣言(declaration)とは, 宣言し
た型と名前を持つ関数(変数)が存在することをコンパイ
ラに教えること
 変数の定義は変数の中身を保存するメモリ領域を確保す
ること
 関数の定義は実際に行う処理を書いたもの

定義があればそこで宣言も行う
 修飾子externは宣言を行うためのもの
 宣言は何回やってもよいが定義は一度だけ

main.cでの宣言と定義


まずhello.hが#includeされているのでvoid hello()が宣言される
ここでコンパイラは引数がnameで返り値がvoidの関数helloが
あることがわかる
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
static void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
main.cでの宣言と定義

name n; では宣言と定義がどちらも行われる
 宣言:nameという型の変数nがあることがわかる
 定義:変数n にメモリを割り当てる
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
printf(“hello, %s.¥n”, n.str);
}
static void hoge(){
……
}
int main(){
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
変数の宣言

変数も宣言だけを行うことができる
 修飾子externを使う
変数の宣言

変数も宣言だけを行うことができる
 修飾子externを使う
 どこかに定義をする必要がある
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
extern int i;
#include <stdio.h>
#include “hello.h”
int i=10;
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
int j;
for(j=0; j<i; j++){
printf(“hello, %s.¥n”, n.str);
}
}
int main(){
i=3;
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
変数の宣言

これでもうまく動く
main.c
hello.h
hello.c
#include “hello.h”
int i;
#ifndef _HELLO_
#define _HELLO_
#include <stdio.h>
#include “hello.h”
int i=10;
int main(){
i=3;
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
typedef struct name{
char* str;
}name;
extern void hello(name);
#endif
void hello(name n){
itn j;
for(j=0; j<i; j++){
printf(“hello, %s.¥n”, n.str);
}
}
変数の宣言

変数にもstaticをつけると他のファイルからは参照できない
 この場合はエラーが出る
main.c
hello.h
hello.c
#include “hello.h”
#ifndef _HELLO_
#define _HELLO_
extern int i;
#include <stdio.h>
#include “hello.h”
static int i=10;
typedef struct name{
char* str;
}name;
extern void hello(name);
void hello(name n){
itn j;
for(j=0; j<i; j++){
printf(“hello, %s.¥n”, n.str);
}
}
int main(){
i=3;
name n;
n.str = “Satoru”;
hello(n);
return 0;
}
#endif
大域変数にはstaticをつけておく
基本的にファイル間で大域変数を共有するのはバグの元
 意図せず同じ名前の大域変数を別のファイルで使ってしま
うと共有されてしまう
 うまく扱うためにはファイルの内部がよくわかっていない
といけないのでモジュール化の観点からもダメ
 どうしても他のファイルからあるファイルの大域変数を操作
したいなら, 値を操作する関数を作る
 get_[変数名]()やset_[変数名]([型] value)など

 局所変数にstaticをつけたときの動作は異なるので調べてください
まとめ
変数や関数は使う前に宣言をしておく必要がある
 また、必ずどこかに定義が必要
 定義は1回だけ
 修飾子externは宣言を行うためのもの
 この場合異なるファイルに定義があってもよい
 他のファイルから見えてほしくない大域変数や関数には
修飾子staticをつける
