fc2ブログ

本当に不変?C言語のconst型修飾子

 変数の値を定数にしたい時、C言語ではconst型修飾子を使います。

#include <stdio.h>
int main( void )
{
    const int value = 10;
    value = 10; //コンパイルエラーが出る
    return 0;
}

 こうすると、「 error C2166: 左辺値は const オブジェクトに指定されています。」の様に、コンパイルエラーが出て、変数を変更することを防げます。この例をみると、const型修飾子は簡単なものだと感じるでしょう。
 しかし、const型修飾子はポインタが絡むと複雑になります。const型修飾子付きポインタ変数を使ったサンプルを見て下さい。

#include <stdio.h>
int main( void )
{
    int value = 10;
    int value1 = 20;
    const int* ptr = &value;
    printf( "初期値%d\n", *ptr );
    //*ptr = 20; //コメントを外すとエラーが発生
    ptr = &value1; //変更してもエラーが出ない
    printf( "現在の値%d\n", *ptr );
    return 0;
}

 期待通り*ptr = 20;の様なコードで変数を直接変更する事は出来ません。しかし、ポインタの値を変更する事により値を変更できます。ポインタの値を変更する事を防ぐには、const型修飾子を使って、変数を定数ポインタとして宣言します。

#include <stdio.h>
int main( void )
{
    int value = 10;
    int value1 = 20;
    int * const ptr = &value;
    printf( "初期値%d\n", *ptr );
    *ptr = 20; //今度は直接値を変更出来てしまう
    //ptr = &value1; //コメントを外すとエラーが発生
    printf( "現在の値%d\n", *ptr );
    return 0;
}

 今度は逆に、ポインタの値は変更できないものの、変数の値は直接変更出来てしまいます。ポインタおよび変数の値の両方を不変にしたければ、const型修飾子を2回指定します。

#include <stdio.h>
int main( void )
{
    int value = 10;
    int value1 = 20;
    const int * const ptr = &value;
    printf( "初期値%d\n", *ptr );
    //*ptr = 20; //コメントを外すとエラーが発生
    //ptr = &value1; //コメントを外すとエラーが発生
    printf( "現在の値%d\n", *ptr );
    return 0;
}

 こうすれば変数を不変にできると、考える人もいるでしょう。しかし、これでもまだ値を変更する方法が存在します。

#include <stdio.h>
int main( void )
{
    int value = 10;
    int * const x = &value;
    const int * const ptr = &value;
    printf( "初期値%d\n", *ptr );

    //他の変数を使用して値を変更
    *x = 20;
    printf( "現在の値%d\n", *ptr );

    //直接値を変更
    value = 100;
    printf( "現在の値%d\n", *ptr );

    return 0;
}

 このサンプルが示すように、const型修飾子は間接的に値を変更する事を防げません。また、参照元の変数を直接変更できる事も忘れてはなりません
 const型修飾子は定数を指定したい時に使用されますが、実際は定数ではありません。constを指定した変数は代入出来ない事を意味しているだけなのです。const型修飾子を指定しても変数は変更される恐れがあります。従って、チーム開発でプログラミングをする場合、コーディングルールが重要だと言えます。
 const型修飾子を使用する際には、十分に注意しましょう。
スポンサーサイト



テーマ : プログラミング
ジャンル : コンピュータ

C言語のif文

 今回はC言語のif文を取り上げます。if文は単純な文ですが、落とし穴が存在します。落とし穴について記述する前に、if文についておさらいします。

#include <stdio.h>
int main()
{
    int i;
    i = 0;
    if ( i ) printf( "0 = true\n" );
    else printf( "0 = false\n" );
    i = 1;
    if ( i ) printf( "1 = true\n" );
    else printf( "1 = false\n" );
    i = -1;
    if ( i ) printf( "-1 = true\n" );
    else printf( "-1 = false\n" );

    return 0;
}

 C言語のif文は括弧内の式の値が0以外ならばthen節の文が実行されます。そして、括弧内の式の値が0で、else節があれば、else節の文が実行されます。
 従って、以下のプログラムは、else節がないので何の文も実行しません。

    i = 0;
    if ( i ) printf( "ここは表示されない\n" );

 では、次のサンプル「何が表示される?」を実行すると、何が表示されるでしょうか?実行する前に考えて下さい。

//何が表示される?
#include <stdio.h>
int main()
{
    int i;
    i = 0;
    if ( i != 0 )
        if ( i >= 0 ) printf( "iは0以上。\n" );
    else printf( "iは0\n" );

    return 0;
}

 答えは・・・何も表示されません。「iは0」と表示されると考えた人が多いと思いますが、elseは最も内側のif文に対応するので、式「if >= 0」の判定結果がelaseの場合に「iは0」と表示されます。このプログラムは意図通り動作しないのです。
 変数が0の時にメッセージを表示したい場合、サンプル「分かりやすい表示」で示す様に{}を付けます。

//分かりやすい表示
#include <stdio.h>
int main()
{
    int i;
    i = 0;
    if ( i != 0 ) {
        if ( i >= 0 ) printf( "iは0以上。\n" );
    } else printf( "iは0\n" );

    return 0;
}

 こうすれば「iは0」と表示されます。この教訓は、if文の入れ子がある場合、複合文を使用するべきです。{}をつけて、複合文にすれば誤読やミスを防げます。
 他にもif文の落とし穴は存在します。次のサンプル「落とし穴」もよくある間違いです。

//落とし穴
#include <stdio.h>
int main()
{
    int i;
    i = 1;
    printf( "変数i = %d\n", i );
    if ( i = 0 ) printf( "iは0\n" );
    else printf( "iは0ではない\n" );
    printf( "変数i = %d\n",  i);

    return 0;
}

 サンプル「落とし穴」は、if文の式でi == 0 とするべき所を、i = 0としているので、変数の値が変えられています。比較するつもりで変数の値を変えてしまえば、以前の値に関係なく何らかの値が代入されるので、予期せぬバグが発生します。
 これを避ける方法は、2つあります。1つめは、コンパイラオプションを指定して、警告をエラーとして扱う方法です。2つめは、比較値を左( if ( 0 = i ) printf( "iは0\n" );)にして、=を使うとコンパイルエラーを発生させる方法です。
 if文の様に、単純な構文でもバグは潜んでいます。十分に注意しましょう。

テーマ : プログラミング
ジャンル : コンピュータ

インクルードファイルの意味を考える

 初めてC言語でのプログラミングを学習した時、Hello worldプログラムを打ったという人が多いと思います。この方法は凄くよいと思うのですが、#include<stdio.h>が具体的に何をするのか、非常に気になりました。同じ事を感じた人も多いと思います。そこで今回はその件について書きます。
 先ずは、Hello worldプログラムに、#include<stdio.h>が絶対必要なのかと考え調べた結果、必ずしも必要ではありませんでした。
 次のプログラムでも実行出来ます。

int printf( const char * _Format, ...); 
int main() { return printf( "Hello world!\n" ); }

 わずか二行です。コンソール画面に「Hello world」と表示するぐらいならば、#includeは必要ありません。このプログラムはprintf関数の定義が必要なだけなので、printf関数を定義すればコンパイルできます。
※コンパイラはこの宣言文を使って、引数をチェックします。その為に、関数の引数の定義が必要となります。
 では、#includeとは何をするものなのでしょうか?それを知るには、#includeの意味をおさらいする必要があります。
 名著「S・P・ハービソン3世とG・L・スティール・ジュニアのCリファレンスマニュアル 」によると#includeは、指定されたソーステキストファイルの内容が、#include指令行の場所に現れたかのように処理するプリプロセッサです。この定義は分かり難いので、実際にプリプロセッサ適用後、ソースの内容がどのように変化するのかを見てみましょう。
 プリプロセッサ適用後の内容を見る方法は、コンパイラにより異なりますが、大概のコンパイラはコンパイルオプションを指定すれば、見る事が出来ます。例えば、VC++ならばPオプションを使用します。そして、GCCならば-save-tempsオプションを指定し、中間ファイルの保存を指示する事により、拡張子iのインクルードファイルを見る事が出来ます。
 このファイルを見ると、#includeが何をしているのか分かると思います。#includeプリプロセッサ指令文は、コンパイラがプログラムをチェックするために、関数の定義を展開しています。
 なお、#include<stdio.h>は必ずしも、stdio.hファイルを指しているとは限らない事に注意して下さい。C言語の規格では、#include<>が本当のファイル名を指している事を要請していません。処理系によっては違うかもしれません。

テーマ : プログラミング
ジャンル : コンピュータ

Cを少し強くつつく2ーmain関数。プログラムのメイン処理だぁ!

前回から引き続き一つのプログラムをつついていくピヨ♪前回で関数プロトタイプをつついたから今回はmain関数をつつくピヨ♪
この関数は一体何ものだろうか?悪戯心に火がついたので、サンプルコードを変えたピヨッ。


int printf(const char *, ...);

int ピヨ♪( void )
{
    printf ( "ピヨッと学習開始♪" );
    return 0;
}


どこが変化したか分かるよね?早速コンパイル♪コンパイル♪


cl /TC /Wall HelloWrold.c

LIBCMT.lib(crt0.obj) : error LNK2019: 未解決の外部シンボル _main が関数 ___tmainCRTStartup で参照されました。
HelloWorld.exe : fatal error LNK1120: 外部参照 1 が未解決です。


結構怒られたピヨォorz。えっと、エラーについて簡単に説明すると、 main関数があると思ったのにぃ。なければリンクできないわ!という風な内容のエラーピヨ。こんなエラーが何故出るのかというと、 main関数から始めるのがお約束だからなんだ。
コンパイラといえども無からEXEファイルを生成できるわけはないピヨ。というのも、今回はないけど複数のプログラムファイルがある時、どこから処理を開始していいのか分からないんだ。それに実は、普通の開発者からは見えないけど、コンパイラがもっと色々仕事をしているピヨ。その仕事の為にお約束が必要というわけさ。だから皆は素直に、影で努力しているVCちゃんをいたわってmain関数をちゃんと定義しよう♪
これでmain関数を定義するの大まかな理由が分かったよね?えっ___tmainCRTStartup関数が気になる?そんな好奇心が旺盛な人は VSでステップイン実行をしてみよう♪そうすれば、VCちゃんがmain関数を動かすために色々な努力をしている様子が見えるピヨ♪暇な時やってみよう♪
今回はこれでお終い♪

テーマ : プログラミング
ジャンル : コンピュータ

Cを少し強くつつく1ーコンパイラに教えてあげよう♪

引き続き前回のサンプルコードをつついていくピヨ♪このサンプルで先ず最初に気になるのが・・・

int printf(const char *, ...);

だよね。これは一体なんだろう?こういった疑問が生じた時は消しちゃえ!そうすれば、コンパイラが文句を言ってくるはずだっピヨ。じゃあ、やってみよう♪


int main(void)
{
    printf ( "ピヨッと学習開始♪" );
    return 0;
}


何を言ってくるかな♪何を言ってくるかな♪・・・・・・あれ???何も言って来ないピヨ。本当に問題なのかな?警告をON(Wallコンパイラオプションを追加)にしてコンパイルしてみたピヨ。


cl /TC /Wall サンプルコードのファイル名
HelloWorld1.c(3) : warning C4013: 関数 'printf' は定義されていません。int 型の値を返す外部関数と見なします。


ちょっと難しいピヨね。VCちゃんが言っている事を簡潔に表現すると「printfなんて知らないわ。仕方が無いからどこかに定義されていると看做すわ。」という事なんだ。今回のサンプルはちゃんと動いているようだけど、それは偶然であって決していい事ではないピヨ。それは何故かと言うと、バグ(間違い)が混入する可能性が非常に高まるからなんだ。こんなふうに・・・


int main(void)
{
    printf ( 96 );
    return 0;
}


きっと、くぅーと言おうとしたんだろうね。だけどコンパイルして実行してごらん。エラーになるよね。こんな風に人間の間違いがチェックされないんだ。今回のプログラムは短いからこれでいいけど、長いプログラムでバグが発生すると凄く大変だよ。デスマーチがきっとやってくるピヨ。そんなの嫌だよね?という事で、消したプログラム(int printf(const char *, ...);)を再び戻してみよう♪そしたら・・・


HelloWorld.c(4) : warning C4047: '関数' : 間接参照のレベルが 'const char *' と 'int' で異なっています。
HelloWorld.c(4) : warning C4024: 'printf' : の型が 1 の仮引数および実引数と異なります。


これで間違えずに済むピヨ♪つまり先ほどのプログラムは、コンパイラに対して使用する関数の定義を教えるためのものなんだ。これを関数プロトタイプ(function prototype)と呼ぶピヨ♪覚えてね♪ 因みに、Cは落とし穴が沢山あるから警告はONにしておく方がいいピヨ。
お疲れ♪もう疲れたと思うから続きは次回書くピヨ♪お楽しみに♪

テーマ : プログラミング
ジャンル : コンピュータ

Cを少し強くつつく0ーさあ学習の開始だ♪

さて今日からC言語をボクと一緒に学習しよう♪今時C言語?と思う人も居るだろうけど、C言語を覚えておくと色々面白いものが作れるし学習の土台になるから、本気で情報処理技術を学ぶのならば習得しておくべき言語だっピヨ♪でも、一番最初にC言語から学習するのはやめた方がいいかもね。C言語は色々な初学者を混乱させる要素があるからね。例えば、変態的な構文とか、思わぬ落とし穴とか、ウインドウ一つ出すのにも手間がかかるとか・・・
だから、C言語をつつく前に、他の簡単なプログラム言語を少しぐらい触っておくことをお勧めするピヨ♪うーんとねぇ、Java、C#、VB.NET、なんかが分かりやすいと思う。Ruby、Python、Perlとかもいいんだけどこの言語達は違う種類の言語なんで、C言語を触ったら硬いと感じるだろうね。
退屈な前置きはこれぐらいにして、早速C言語のサンプルをつつこう♪やっぱ餌(プログラム)がないとね♪


/*VCに特化したサンプル*/
int printf(const char *, ...);

int main(void)
{
    printf ( "ピヨッと学習開始♪" );
    return 0;
}


えっ?何故Hello Worldじゃないのかって?そんなの退屈だし、#includeの説明をおざなりにしているからあれは問題あると思うんだ。という事で、#includeを使わないC言語のサンプルをどうぞ召し上がれ♪
VCのインストールは完了しているものとして話しを進めるピヨ。このコードをコマンドラインでコンパイルしてみよう。


cd /d サンプルコードを保存したディレクトリ
cl /TC サンプルコードのファイル名


これでコンパイルできるピヨ♪正常にビルドできたら、EXEファイルがサンプルコードと同じ場所に保存されているからコマンドラインにEXEファイルの名前を入力してEnterキーを押そう!そうすれば、「ピヨッと学習開始♪」と表示されるピヨ♪じゃあ、このサンプルコードを解説するピヨ。おっと時間だ。いいところだけど長すぎるから次回解説を書くピヨ♪また近いうちに会おう♪

テーマ : プログラミング
ジャンル : コンピュータ

C言語の奥をつつく1ーmain関数は初めの関数じゃない!

Cの奥をつつくためにまずやるべきことは、最小の情報を解析する事ピヨ。
だからボクは次のCファイルを用意したピヨ。


//mini.c
void main(){}


どう?最小でピヨピヨ?という事で早速コンパイルしよう。一々開発環境で新規プロジェクトの作成をするのは面倒だからコマンドラインでコンパイルしよう!
まずはVS Toolの方のコマンドラインプロンプトを立ち上げてから次の手順でコンパイルしてね。

cd /d ファイルパス
cl mini.c /FA

次回からはコンパイル方法を省略するからよく覚えておいてね。じゃあ早速asmファイルを見てみよう。※画面の都合で一部改行しています


; Listing generated by Microsoft (R) 
;Optimizing Compiler Version 15.00.21022.08 

	TITLE	ファイル名
	.686P
	.XMM
	include listing.inc
	.model	flat

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

PUBLIC	_main
; Function compile flags: /Odtp
_TEXT	SEGMENT
_main	PROC
; File ファイル名
; Line 1
	push	ebp
	mov	ebp, esp
	xor	eax, eax
	pop	ebp
	ret	0
_main	ENDP
_TEXT	ENDS
END


流石最小のコード!凄くシンプルピヨ♪このコードがやっている事は、 「EBPレジスタの値をスタックに退避して、すぐに値を戻して、もと来た場所へ戻る。」だけなんだ。ちなみに、xor eax, eaxは戻りを0にするための命令ピヨ。排他的論理和は片方だけ1の場合のみtrueとなるから必ず0となるわけさ。
※xchg eax, eaxと見間違っていたから訂正しました。 この最小のコードから分かる事はmain関数以外にも処理がある事ピヨ。何で分かるかって?だって、retで戻っているんだもん。これでわかったと思うけど、C言語はmain関数以外にも様々な処理があるんだ。それは何の処理かと囀ると、環境変数の初期化スタックに必要なヒープの確保・・・などのCを実行する上で必要な準備ピヨ。それで、何で終わったら戻るのかというと処理が正常か判断するためなんだ。C言語を触った事ある人ならば最後にreturn 0;とかするのは知っているよね?その値を受け取る処理が必要というわけさ。
今回はこれで終わり。

テーマ : プログラミング
ジャンル : コンピュータ

C言語の奥をつつく0ー始まりは何時もコンパイラオプション。

Cの奥をつつくシリーズを開始するピヨ。最近深いところをつついていなかったのでムズムズしていたんだよね。まずはつつき方を囀るピヨ。今回はひとまずVCの扱い方を説明すると、次のコンパイラオプションが必要だピヨ。オプションと生成されるファイルを一覧に下から見てね。


【Cの奥をつつくためのオプション一覧(VC)】
  • アセンブリ コードのみ (/FA)・・・asm、obj、exe。
  • アセンブリ コードとコンピュータ語コード (/FAc)・・・cdd、obj、exe。
  • アセンブリ コードとソース コード (/FAs)・・・asm、obj、exe。
  • アセンブリ コード、コンピュータ語コード、ソース コード (/FAcs)・・・cdd、obj、exe。


この一覧を見てまず分かる事はなんらかのファイル+ obj + exeの各ファイルが生成される事ピヨ。つまり、これらのオプションは、このなんらかのファイルだけが変わると思えばいいピヨ。それじゃ、生成されるasmファイルとcddファイルをつつくピヨ。
asmファイルの正式名はAssembler Sourceファイルで、その中身はMASMアセンブラのコードピヨ。ひとつ注意が必要なのは、FAとFAsでファイル内容が変わる事ピヨ。FAの場合はアセンブラコードのみだけど、FAsの場合はC言語のコードも併記されているピヨ。これは便利だね!でも残念な事に、少々読みにくいんだ・・・
次はcddファイルをつつくピヨ。このファイルの正式名称はC/C++ Code Listingファイルだピヨ。それで肝心の中身はというと、機械語とMASMアセンブラコードピヨ。これが見やすいんだ。機械語とアセンブラはほぼ一対一で対応しているからね。
今回はこれで終わり。次回から実際のCコードをつつくから楽しみにしてね。 おおと、忘れると事だった。開発環境からこのオプションを指定するには・・・
プロジェクトのプロパティ→C/C++→出力ファイル→アセンブリの出力
ピヨォッ!みんな、楽しんでね。じゃあ今度こそバイバイ。よいハックを!

テーマ : プログラミング
ジャンル : コンピュータ

Cをつつくー基礎も大事。はじめましてC。

インドリ「hello c。君は結構な年だけど、OS開発とかまだまだ君の力を借りることがいっぱいあるね。」

#include 

int main(void) {
    char i;
    printf( "hello indori\n" );
    for ( i = 0; i < 10; i++ ) printf( "%d ", i );
    printf( "\n" );
    printf( "good-by\n" );
}

テーマ : プログラミング
ジャンル : コンピュータ

プロフィール

インドリ

Author:インドリ
みなさん、はじめまして、
コンニチハ。

ボクは、無限の夢(infinity dream)を持つネタ好きな虹色の鳥インドリ(in dre)です。
色々な情報処理技術を啄ばむから楽しみにしてね。

http://twitter.com/indori
は別人による嫌がらせ行為です。
私とは関係ないので注意して下さい。
次はなりすましブログなどをするかもしれませんが、ここ以外でブログをするつもりがないので、ここ以外にインドリのブログがあったとしても無視してください。


何度言っても分からない人がいるので、ここにコメント欄へ書き込むときの注意事項を書きます。


一、社会人としてのマナーをわきまえましょう。
一、妄想に基づく書き込みを止めてください。
一、暴言の類は書かないで下さい。
一、某誹謗中傷サイトの書き込みは彼らの妄想に基づく書き込みですから無視して、ここへ書き込まないで下さい。
一、コメント書く前に他のコメントよく読んでから行って下さい。
一、言いがかかり等の行為を禁止します。
一、その他常識的に考えて迷惑なコメントはしないで下さい。


以上のルールを守れない人のコメントは削除します。



利用上の注意
ここに紹介してある文章およびプログラムコードは正確であるように心がけておりますが、内容を保証するものではありません。当サイトの内容によって生じた損害については、一切の責任を負いませんので御了承ください。


執筆したCodeZineの記事


【VB.NETで仮想CPUを作ろう】

  1. VB.NETで仮想CPUを作ろう
  2. レジスタの実装
  3. 仮想CPUのGUI化
  4. テストドライバの改良
  5. CPUの基礎動作の実装
  6. MOV命令の実装
  7. ADD命令実装
  8. SUB命令実装
  9. INC命令&DEC命令の実装と命令長
  10. MLU命令の実装とModR/Mについて
  11. DIV命令の実装とイベント設計について
  12. 機械語駆動式 関数電卓を作ろう!
  13. 機械語駆動式 関数電卓を作ろう! 解答編(前半)
  14. 機械語駆動式 関数電卓を作ろう! 解答編(後半)


【仮想ネットワーク実装でTCP/IPを学ぼう】
  1. TCP/IPの基礎と勘所
  2. ネットワークアクセス層の勘所
  3. インターネット層の勘所
  4. トランスポート層の勘所
  5. アプリケーション層の勘所
  6. セキュリティの基礎と仮想ネットワークの仕様
  7. GDI+と独自プロトコルの定義



【並列化】
インテル Parallel Studioを使って並列化プログラミングを試してみた
並列プログラミングの効率的なデバッグを実現する「Parallel Inspector」


【TBBシリーズ】
  1. インテル スレッディング・ビルディング・ブロックの概要
  2. インテルTBBから学ぶループの並列化
  3. スレッドセーフとインテルTBBのコンテナ
  4. インテルTBBのスレッドクラス


【OpenMPシリーズ】
  1. OpenMPの基礎構文
  2. OpenMPの実行時ライブラリと並列ループ
  3. OpenMPのメモリモデルとfork- joinモデル

最近の記事
最近のコメント
月別アーカイブ
カテゴリ
Ada (9)
COBOL (5)
C (9)
C++ (11)
C# (370)
D (25)
Java (8)
Perl (1)
Ruby (14)
PHP (2)
Boo (2)
Cobra (2)
LISP (6)
F# (33)
HTML (0)
XHTML (0)
CSS (0)
XML (0)
XSLT (0)
Scala (4)
WPF (0)
WF (2)
WCF (0)
LINQ (4)
MONO (5)
Linux (0)
MySQL (0)
ブログ内検索
リンク
最近のトラックバック
RSSフィード
ブロとも申請フォーム

この人とブロともになる

QRコード
FC2カウンター