[gcc最適化]printfがputs呼び出しに置き換えられる場合がある

C言語には、文字表示のためにprintf関数が有ります。

printfは%指定で変数を書式指定付きで埋め込める高機能な関数ですが、高機能な分、処理が遅いというデメリットもあり、書籍によっては “固定の文字を出すだけであればprintf()の替わりにputs()を使用すべし” というHowToが書かれてたりもします。


と今まで思ってましたが…

gccの出力を見ると、時によっては”printfをputs呼び出しに自動で変換してくれる場合がある”ようです。



雰囲気では、printf( “hello\n” );のように、”変数を含まず、かつ、改行で終わる場合”が対象っぽいのですが、どのような場合でprintf呼び出しの最適化が行われるのか、確認してみました。


というわけで、確認用のプログラムです。
面倒なので、確認結果(最適化後の置き換え関数)も先にコメントで書いておきました。

#include<stdio.h>
 
int main()
{
    printf( "test0\n" );                // puts
 
    printf( "test1" );                  // printf
 
    printf( "test2:%s\n", "test2" );    // printf
 
    printf( "%s", "test3\n" );          // puts
 
    printf( "%s\n", "test4" );          // puts
 
    printf( "test5%s\n", "test5" );     // puts
 
    printf( "A" );                      // putchar
 
    printf( "%c", '6' );                // putchar
 
    printf( "%c\n", '7' );              // printf
 
    printf( "%%" );                     // printf
}




説明の順番が前後しますが、上記コードのアセンブラ出力です。

$ gcc -S opt.c
 
 
$ cat opt.s
    .file   "opt.c"
    .section    .rodata
.LC0:
    .string "test0"
.LC1:
    .string "test1"
.LC2:
    .string "test2:%s\n"
.LC3:
    .string "test2"
.LC4:
    .string "test3"
.LC5:
    .string "test4"
.LC6:
    .string "test5%s\n"
.LC7:
    .string "test5"
.LC8:
    .string "%c\n"
.LC9:
    .string "%%"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $16, %esp
    movl    $.LC0, (%esp)
    call    puts
    movl    $.LC1, %eax
    movl    %eax, (%esp)
    call    printf
    movl    $.LC2, %eax
    movl    $.LC3, 4(%esp)
    movl    %eax, (%esp)
    call    printf
    movl    $.LC4, (%esp)
    call    puts
    movl    $.LC5, (%esp)
    call    puts
    movl    $.LC6, %eax
    movl    $.LC7, 4(%esp)
    movl    %eax, (%esp)
    call    printf
    movl    $65, (%esp)
    call    putchar
    movl    $54, (%esp)
    call    putchar
    movl    $.LC8, %eax
    movl    $55, 4(%esp)
    movl    %eax, (%esp)
    call    printf
    movl    $.LC9, %eax
    movl    %eax, (%esp)
    call    printf
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
    .section    .note.GNU-stack,"",@progbits



この呼び出し関数変更による最適化は、1パターンを除いて、gccに-O0オプション(-O0:最適化しない)をつけた場合でも行われました。



結果を見ると、下記のパターンの時にputs関数に置き換わりました。

    printf( "test0\n" );                // puts
    printf( "%s", "test3\n" );          // puts
    printf( "%s\n", "test4" );          // puts
    printf( "test5%s\n", "test5" );     // puts (-O0だとprintfになる)


仮に%s等の置換部があったとしても、コンパイル時に表示すべき文字列が確定してればputsに置き換えてくれるようです。
4つ目のパターンだけは、なぜか-O0と最適化オプション無しで出力が異なっていました。


以下のパターンでは最適化は行われません。

    printf( "test1" );


これは、出力文字の終端が改行文字ではないパターンは、putsに置き換えできないからです。
putsは引数で与えられた文字列に改行文字を加えて、標準出力に印字します。



また、出力結果が1文字で、かつ末尾に改行が無い場合はputcharに置き換わります。
(例には有りませんが、”\n”だけの出力もputcharになりました)

    printf( "A" );                      // putchar
    printf( "%c", '6' );                // putchar



また、以外な事に”%%\n”や”%%”だと、printfそのままです。

    printf( "%%\n" );                   // printf


これは、putchar()に代替できそうな気もしますが、ダメでした
gccのソースを見たところ以下のコメントがあったので、現状では対応できてないみたいです。
/* We can’t handle anything else with % args or %% … yet. */





なお、このprintf->putchar/putsへの最適化ですが、gccのソースコード的には、c-common.cにあるc_expand_builtin_printf()という関数で行われているようです。

ちょっと長いですが、c_expand_builtin_printf関数のソースをまるっと引用しますので、興味がある方は見てみるのも良いかと思います。
(当然ながら,c_expand_builtin_printf関数に対するのライセンスはGPLです)


細かい事はさておき、コメントを見ながら流れを追うとある程度の雰囲気はつかめるかと思います。

static rtx
c_expand_builtin_printf (arglist, target, tmode, modifier, ignore, unlocked)
     tree arglist;
     rtx target;
     enum machine_mode tmode;
     enum expand_modifier modifier;
     int ignore;
     int unlocked;
{
  tree fn_putchar = unlocked ?
    built_in_decls[BUILT_IN_PUTCHAR_UNLOCKED] : built_in_decls[BUILT_IN_PUTCHAR];
  tree fn_puts = unlocked ?
    built_in_decls[BUILT_IN_PUTS_UNLOCKED] : built_in_decls[BUILT_IN_PUTS];
  tree fn, format_arg, stripped_string;
 
  /* If the return value is used, or the replacement _DECL isn't
     initialized, don't do the transformation.  */
  if (!ignore || !fn_putchar || !fn_puts)
    return 0;
 
  /* Verify the required arguments in the original call.  */
  if (arglist == 0
      || (TREE_CODE (TREE_TYPE (TREE_VALUE (arglist))) != POINTER_TYPE))
    return 0;
 
  /* Check the specifier vs. the parameters.  */
  if (!is_valid_printf_arglist (arglist))
    return 0;
 
  format_arg = TREE_VALUE (arglist);
  stripped_string = format_arg;
  STRIP_NOPS (stripped_string);
  if (stripped_string && TREE_CODE (stripped_string) == ADDR_EXPR)
    stripped_string = TREE_OPERAND (stripped_string, 0);
 
  /* If the format specifier isn't a STRING_CST, punt.  */
  if (TREE_CODE (stripped_string) != STRING_CST)
    return 0;
 
  /* OK!  We can attempt optimization.  */
 
  /* If the format specifier was "%s\n", call __builtin_puts(arg2).  */
  if (strcmp (TREE_STRING_POINTER (stripped_string), "%s\n") == 0)
    {
      arglist = TREE_CHAIN (arglist);
      fn = fn_puts;
    }
  /* If the format specifier was "%c", call __builtin_putchar (arg2).  */
  else if (strcmp (TREE_STRING_POINTER (stripped_string), "%c") == 0)
    {
      arglist = TREE_CHAIN (arglist);
      fn = fn_putchar;
    }
  else
    {
      /* We can't handle anything else with % args or %% ... yet.  */
      if (strchr (TREE_STRING_POINTER (stripped_string), '%'))
  return 0;
 
      /* If the resulting constant string has a length of 1, call
         putchar.  Note, TREE_STRING_LENGTH includes the terminating
         NULL in its count.  */
      if (TREE_STRING_LENGTH (stripped_string) == 2)
        {
    /* Given printf("c"), (where c is any one character,)
             convert "c"[0] to an int and pass that to the replacement
             function.  */
    arglist = build_int_2 (TREE_STRING_POINTER (stripped_string)[0], 0);
    arglist = build_tree_list (NULL_TREE, arglist);
 
    fn = fn_putchar;
        }
      /* If the resulting constant was "string\n", call
         __builtin_puts("string").  Ensure "string" has at least one
         character besides the trailing \n.  Note, TREE_STRING_LENGTH
         includes the terminating NULL in its count.  */
      else if (TREE_STRING_LENGTH (stripped_string) > 2
         && TREE_STRING_POINTER (stripped_string)
         [TREE_STRING_LENGTH (stripped_string) - 2] == '\n')
        {
    /* Create a NULL-terminated string that's one char shorter
       than the original, stripping off the trailing '\n'.  */
    const int newlen = TREE_STRING_LENGTH (stripped_string) - 1;
    char *newstr = (char *) alloca (newlen);
    memcpy (newstr, TREE_STRING_POINTER (stripped_string), newlen - 1);
    newstr[newlen - 1] = 0;
 
    arglist = fix_string_type (build_string (newlen, newstr));
    arglist = build_tree_list (NULL_TREE, arglist);
    fn = fn_puts;
  }
      else
  /* We'd like to arrange to call fputs(string) here, but we
           need stdout and don't have a way to get it ... yet.  */
  return 0;
    }
 
  return expand_expr (build_function_call (fn, arglist),
          (ignore ? const0_rtx : target),
          tmode, modifier);
}


cygwinで1秒未満のスリープを行う

C言語でプログラムを作成時にスリープ処理を行いたい場合、Cの標準ライブラリ関数だとsleep()関数を使用します。ですが、sleep()関数で指定する値の単位は秒なので、1秒未満のスリープを行う事が出来ません。

Visual C++等、Windows環境だとWin32APIにSleep()というものがあり、ミリ秒単位でのスリープが行えるのですが、cygwinでもこの関数が使用できるようです。

#include<stdio.h>
#include<windows.h>
 
int main()
{
	printf( "test1\n" );
	Sleep( 100 ); /* 100msecスリープ */
	printf( "test2\n" );
}



上記のようにwindows.hをインクルードすれば、win32のAPIがコールできます。


ヘッダの場所は64bit環境だと以下のフォルダにあるようです。
 -> /usr/x86_64-w64-mingw32/sys-root/mingw/include/windows.h

32bitの場合は、環境が手元にないので憶測ですが、おそらく/usr/i686-pc-mingw32以下のヘッダを使用するような気がします。

[C言語]getchar()関数は,1文字入力する毎に処理が走る訳ではない

C言語の入門書を見るとgetchar()関数の説明として、”キーボードから1文字入力を読み取る”と書かれていることが多いです。

下記のプログラムは、getchar()の動作確認用プログラムなのですが、実行してみると”キーボードから1文字入力を読み取る”という説明とは異なり、若干の違和感があります。

#inculde <stdio.h>
 
int main( void )
{
    int data;
 
    while( 1 ) {
        // 1文字入力
        data = getchar();
        if ( data == EOF ) {
            break;
        }
 
        // 入力された文字を出力
        putchar( data );
    }
 
}



このプログラムの実行結果は以下の通りです

$ gcc echoback.c -o echoback
 
$ ./echoback 
123
123
^d
 
$



出力結果だけからだと分かり辛いのですが、”キーボードから123と入力後、Enterを押すと文字”123″が表示される”という振る舞いをしています。

getcharの”キーボードから1文字入力を読み取る”をそのまま信じると、1文字単位で入力文字が反映されるはずなので”112233″となりそうなものですが、実際は行単位でのエコーバックになります。
gdbを使ってステップ実行してみると、Enterを入力した時点でwhileが一気に3度回り、その後getchar()の入力待ちとなりました。

gcc -g echoback.c -o echoback
 
gdb echoback
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/user1/work/echoback...done.
(gdb) list
1   #include<stdio.h>
2   
3   int main( void )
4   {
5       int data;
6   
7       while( 1 ) {
8           // 1文字入力
9           data = getchar();
10          if ( data == EOF ) {
 
 
(gdb) b 7                                               #ブレークポイントを7行目にセット
Breakpoint 1 at 0x804841d: file echoback.c, line 7.
(gdb) run
Starting program: /home/user1/work/echoback 
 
Breakpoint 1, main () at echoback.c:9
9           data = getchar();                           #キー入力待ち. 
                                                        #"123Enter"を入力するまで進まない
(gdb) next
123
10          if ( data == EOF ) {
(gdb) next
15          putchar( data );                            #1文字目の出力
(gdb) next
16      }
(gdb) next
 
Breakpoint 1, main () at echoback.c:9
9           data = getchar();                           #ここではキー入力待ちにならない
(gdb) next
10          if ( data == EOF ) {
(gdb) next
15          putchar( data );                            #2文字目の出力
(gdb) next
16      }
(gdb) next
 
Breakpoint 1, main () at echoback.c:9
9           data = getchar();
(gdb) next
10          if ( data == EOF ) {
(gdb) next
15          putchar( data );                            #3文字目の出力
(gdb) next
16      }
(gdb) next
 
Breakpoint 1, main () at echoback.c:9
9           data = getchar();
(gdb) next
10          if ( data == EOF ) {
(gdb) next
15          putchar( data );                            #4文字目の出力(Enter)
(gdb) next
123                                                     #ここで纏めて文字が表示される
16      }
(gdb) next
 
Breakpoint 1, main () at echoback.c:9
9           data = getchar();                           #再度入力待ち
(gdb) nex
Ambiguous command "nex": next, nexti.
(gdb) next



このような振る舞いになるのは、C言語では標準入力(stdin)が入力された文字をバッファリングしており、getchar()関数を使用する場合は、Enterが押されるまでプログラム側ではそのバッファを認識できないのが原因です。

また、標準出力(stdout)も出力バッファがあるので、出力側が1文字単位ではなく1行纏めて出ていたのもこれが原因です。

プログラムの経験がある人から見ると普通の振る舞いですが、C言語で初めてプログラムを学ぶ場合は、都度処理しない事を不思議に思うかもしれません。
コンピュータはCPU内部の計算処理に比べると、入出力処理は非常に時間が掛かるため、処理速度向上のためにこのようなバッファが設けられています。

74HC4511とBQ-N516RDで,4桁の7セグLEDをコントロールする

7セグのLEDであるBQ-N516RDと、BCDから7セグLED用デコーダICの74HC4511を組み合わせて、PIC等のマイコンからLED表示を行わせる回路を作成します。

今回は、PIC部分後回しにして、BQ-N516RDと74HC4511の仕様とピンアサインをチェックします。

まずは価格のチェックです。
どちらもマルツパーツ館で購入できます(ネットからも買えます)

BQ-N516RD

7セグメントLED(4桁・赤・カソードコモン)
472円/個


74HC4511

BCD-to-Seven Segment Latch/Decoder/Driver
157円/個




それでは、各パーツのスペックを確認していきます。

BQ-N516RDのスペック




データシートを確認すると、それぞれのピンアサインは以下の通りです。


一覧表にすると、以下のようになります。

BQ-N516RD
------------------
 
Pin  Function
---  ---------
  1  e
  2  d
  3  Dot
  4  c
  5  g
  6  GND(D4)
  7  b
  8  GND(D3)
  9  GND(D2)
 10  f
 11  a
 12  GND(D1)



a~eの各信号線が、7セグダイオードのどの位置に対応するかは、下の図を見ると分かりやすいです。LEDの位置とアルファベットの対応は、基本的に12時方向から時計回りと覚えておくと分かりやすいです。
また、下側の上位桁に有るピンが1番ピンとなります。



また、このパーツは4桁の数字を表示できますが、どの桁を表示させるかは6,8,9,12ピンで指定します。
このピンのうち特定の1ピンをLow、残りをHighにすることで、表示桁を指定できます。
4桁同時に表示させる事は出来ないので、各桁を素早く順繰りに表示させていきます。そうすると人は残像で各桁が同時に点灯していると錯覚し、見かけ上4桁の表示が可能になります。
このように、順番に各桁を点灯させていく方ほうを”ダイナミック点灯”と呼びます。
ダイナミック点灯を行う場合は、各桁の表示切り替えタイミングが遅いとちらついて見えてしまうので、最低でも60Hz程度の速度は必要です。

また、7セグのLEDには、アノードコモン方式のものと、カソードコモン方式のものがあります。
BQ-N516RD各桁が集約しているコモン側が、LEDのカソードになっているのでカソードコモンとなります。
カソードコモンの場合は、コモン端子をLow,a~gの端子をHighにすることで点灯させることが出来ます。

一方アノードコモンの場合はダイオードの方向が逆になっているので、コモン端子をHigh,a~gの端子をLowにすると点灯させることが出来ます。



74HC4511のスペック




次は74HC4511の仕様を確認します。

まずはピンアサインです。


こちらも一覧にすると以下のようになります。

74HC4511
------------------
 
Pin  Function
---  ---------
  1  B   (in)
  2  C   (in)
  3  ^LT
  4  ^BL
  5  LE
  6  D   (in)
  7  A   (in)
  8  GND
  9  e
 10  d
 11  c
 12  b
 13  a
 14  g
 15  f
 16  Vcc



BCD to 7segデコーダなので、基本的な使い方としてはA,B,C,DにBCDのデータを入力として与えると、
7セグ用の出力(前述のa~g)を出してくれるという仕組みです。

また、このICはそれ以外にもLE, ^BI, ^LTという3本の信号線を持っています。
この3つの信号線は以下の意味を持っています。

LE: Latch Enable (入力を一時ラッチする)
LT: Lamp Test    (表示テスト)
BL: BLanking     (明るさ調整,パルス駆動時のターンオフ用)



通常、数字を表示させる場合はLE, ^BI, ^LTを各々以下の値にします

LE   Low
^BI  High
^LT  High



LE(Latch Enable)のpinはLE=Lowの時にA,B,C,Dに覚えさせたい値をセットした後、LE=HighにするとLowだった時にセットされた値を保持します。
同期式の回路を組みたい場合に便利です。

入力ピンのA~Cについては、BCD形式なので2進数として入力すれば良いです。
以下、入力と出力の対応表になります。




2つのパーツを組み合わせる


マイコン等からの出力データがBCDデータ(4bit)+桁指定(4bit)の場合、配線は以下のようになります。


    マイコン      74HC4511        BQ-N516RD
    ------------  --------------  --------------
 
    data          Pin  Function   Pin  Function
    ----          ---  ---------  ---  ---------
    bit1            1  B   (in)   
    bit2            2  C   (in)   
+5V                 3  ^LT        
+5V                 4  ^BL        
GND                 5  LE         
    bit3            6  D   (in)   
    bit0            7  A   (in)   
GND                 8  GND        
                    9  e            1  e
                   10  d            2  d
                   11  c            4  c
                   12  b            7  b
                   13  a           11  a
                   14  g            5  g
                   15  f           10  f
+5V                16  Vcc        
GND                                 3  Dot
    keta1                          12  GND(D1)
    keta2                           9  GND(D2)
    keta3                           8  GND(D3)
    keta4                           6  GND(D4)



回路図にすると、以下のような感じになります。

(この回路図はBSch3Vというフリーソフトで作図しました。作図時の作業内容はこちら)


上記パターンだとマイコン側で8本のピンが必要ですが、LS74139のデコーダを使用すれば、桁指定のピンを4本から2本に減らすことが出来ます。

接続する信号線が多いので、間違えないように注意しながら配線してください。
ブレッドボードで組もうとすると線がさせる場所が5個しかないので、かなり煩雑になります。

また、LEDは当然ながら電流制限用として500Ω程度の抵抗をはさむ必要があります。
各LEDに対して別個の抵抗を使用すると、抵抗値のにばらつきがある為、各LEDの明るさに差が出る可能性があります。
これを避けるには集合抵抗を使用すると、明るさの差が少なくなりますが、ブレッドボードで組む場合は、配線がややこしくなるので、さらに面倒になります。

[C言語]switch文のcase句の後にスペースが必要な場合/不要な場合

人が書いたプログラムを見ていたとき、switch文のcase句の書き方に違和感があるコードが有りました。
要点だけ抜粋すると、以下の内容です。

    switch( data ) {
            ...
        case'1':    // <- caseと'1'の間に空白がない!!
            ...
            break;
        case'2':
            ...
            break;
        default:
            ...
            break;
    }


caseと条件値の間にはスペースが必要だと思ってたのですが、上記のコードはどうやらコンパイルも正常に行え、振る舞いも想定どおりな様です。


気になったので、このへんの振る舞いに関する確認用プログラムを作成してみました。

#include <stdio.h>
#define CS4 '4'
 
int main()
{
    int data;
 
    printf( "please input data>" );
    scanf( "%d", &data );
 
    switch( data ) {
        case 0:
            printf( "pos0\n" );
            break;
        case1:      // 空白を入れない
            printf( "pos1\n" );
            break;
        case '2':   // '2' = 0x32 = 50
            printf( "pos2\n" );
            break;
        case'3':    // '3' = 0x33 = 51
            printf( "pos3\n" );
            break;
        caseCS4:    // define値で空白を入れない
            printf( "pos4\n" );
            break;
        case0x05:   // 16進数で空白を入れない
            printf( "pos5\n" );
            break;
        default:
            printf( "default\n" );
            break;
    }
}



実行結果:

$ gcc switch_test.c  -o switch_test
 
$ ./switch_test 
please input data>0
pos0
 
$ ./switch_test 
please input data>1
default
 
$ ./switch_test 
please input data>50
pos2
 
$ ./switch_test 
please input data>51
pos3
 
$ ./switch_test 
please input data>52 
default
 
$ ./switch_test 
please input data>5
default




各パターンの動作結果に対して、なぜそうなったのかを考えて見ます

case 0
    これは正常パターンなので、当然動作します。
 
case1:
    "case1"というgotoラベルと解釈されるので、正しく動作しません。
    後述しますが、アセンブラの結果を見ても"pos1"の出力処理自体が省かれています。
 
case '2':
    これも正常パターン。動作します。
 
case'3':
    これが当初違和感を感じたパターンです。
    gccでコンパイルしましたが、はやり正常に動作してます。
 
caseCS4:
    コードを書いてたときは、このパターンはいけるのでは...と思ってたのですが、NGでした。
    良く考えてみたら、"caseCS4"というgotoラベルと認識されるだけなので、ダメで当然ですね。
 
case0x05:
    これもダメでした。




で、こちらがアセンブル出力です。
pos1, pos4, pos5に関しては、処理自体が無かったものと見なされてます(コードまで追わなくても.LCxの定数部だけ見れば一目瞭然です)。
ジャンプされる可能性がないgotoラベルと見なされたからだと思われます。

$ gcc switch_test.c  -S
 
 
$ cat switch_test.s
    .file   "switch_test.c"
    .section    .rodata
.LC0:
    .string "please input data>"
.LC1:
    .string "%d"
.LC2:
    .string "pos0"
.LC3:
    .string "pos2"
.LC4:
    .string "pos3"
.LC5:
    .string "default"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    movl    $.LC0, %eax
    movl    %eax, (%esp)
    call    printf
    movl    $.LC1, %eax
    leal    28(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    __isoc99_scanf
    movl    28(%esp), %eax
    cmpl    $50, %eax
    je  .L4
    cmpl    $51, %eax
    je  .L5
    testl   %eax, %eax
    jne .L7
.L3:
    movl    $.LC2, (%esp)
    call    puts
    jmp .L8
.L4:
    movl    $.LC3, (%esp)
    call    puts
    jmp .L8
.L5:
    movl    $.LC4, (%esp)
    call    puts
    jmp .L8
.L7:
    movl    $.LC5, (%esp)
    call    puts
    nop
.L8:
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
    .section    .note.GNU-stack,"",@progbits




というわけで、直感に反する振る舞いは”caseの後に空白をおかずに文字定数が続く”パターンだけでした。
この振る舞いになる原因をJIS X3010の規格書から調べてみたのですが、分かりませんでした…
なぜだろう??

[C言語入門]単純作業を繰り返しさせるためにfor文を使う

前回のプログラムでは、if文やswitch文を使って処理を分岐させる方法を学びました。
今回は、繰り返し処理を説明します。
プログラム言語において、繰り返し処理は、ループ処理とも呼ばれています。

コンピュータは大量の処理/データを、繰り返し作業させるのが得意です。
人とは違ってコンピュータは、どんなに大量のデータであっても途中であきらめずに作業してくれますし、途中で集中力が途切れて間違えてしまう事も有りません。

そんな繰り返し処理ですが、C言語に限らず、プログラムでのループ処理には大きく分けて2つのパターンがあります。

1.指定した回数繰り返す
2.指定した条件が満たされるまで繰り返す



プログラムの話に入る前に、上記の2パターンの例を日本語で挙げてみます。
例えば銀行の利子を計算するプログラムを作っているとして…

1.指定した回数繰り返す
    年利3%で10万円貯金したら、10年後にいくらになっているか?
 
2.指定した条件が満たされるまで繰り返す
    年利3%で10万円貯金したら、何年後に20万円になるか?



前者は”10年後”という条件が決まっているので、何らかの処理を10回行わなければならない事が分かります。また、後者については”預金が20万円になるまで”という条件は分かっていますが、それが何年後になるかは不明なので何回繰り返すかが事前に分かりません。


今回は、前者の”指定した回数繰り返す”を行うのが得意なfor文の使い方を学びます。

というわけで、1つ目のサンプルプログラムです。

#include <stdio.h>
 
int main()
{
    int loop;
 
    for ( loop = 0; loop < 10; loop = loop+1 ) {
        printf( "%d回目の処理です\n", loop );
    }
 
    return 0;
}



このプログラムをコンパイル & 実行すると以下の結果になります。

$ gcc -Wall -g -o test01 test01.c
 
$ ./test01
0回目の処理です
1回目の処理です
2回目の処理です
3回目の処理です
4回目の処理です
5回目の処理です
6回目の処理です
7回目の処理です
8回目の処理です
9回目の処理です


これまでの知識だとprintf関数を10回も書かなければならないところだったのが、for文のおかげで1つ書けば済むようになりました。


for文の構文ですが、以下のような形になっています。

for ( 初期処理; 終了条件; カウンタの更新 ) {
    処理
}



動作については、以下のルールで処理が行われます。

Step1
    "初期処理"を実行する
 
Step2
    "終了条件"を判定する
    条件が成立しない場合は、for内の処理を行わずにfor文の実行を終える
    条件が成立する場合は、for内の処理を行わずにfor文を実行する
      ※但し、for文内にbreak文がある場合は、そこでfor文の実行を終える
      ※但し、for文内にcontinue文がある場合は、そこでStep3に移る
Step3
    "カウンタの更新"処理を行い、Step2に戻る


Step2で終了条件を判定しますが、初回で条件に一致しなければループを一回も実行しない場合も有りますし、いつまでも一致し続ける場合は無限ループになります。




初期処理、終了条件、カウンタの更新は書かずに省略することも出来ます。

#include <stdio.h>
 
int main()
{
    int loop;
 
    // 初期処理を省略
    printf( "----パターン1----\n" );
    loop = 0;
    for ( ; loop < 5; loop = loop+1 ) {
        printf( "パターン1  %d回目の処理です\n", loop );
 
    }
 
    // カウンタ更新処理を省略
    printf( "----パターン2----\n" );
    for ( loop = 0; loop < 5; ) {
        printf( "パターン2  %d回目の処理です\n", loop );
        loop = loop+1;   // ここでカウンタを更新してみた。
    }
 
 
    return 0;
}



実行結果はこちらです。

$ ./test01
----パターン1----
パターン1  0回目の処理です
パターン1  1回目の処理です
パターン1  2回目の処理です
パターン1  3回目の処理です
パターン1  4回目の処理です
----パターン2----
パターン2  0回目の処理です
パターン2  1回目の処理です
パターン2  2回目の処理です
パターン2  3回目の処理です
パターン2  4回目の処理です




ループカウンタの更新条件を”loop = loop + 1″と書いていますが、ここは”loop++”と書く事も出来ます。
この”++”はインクリメント演算子と呼ばれており、変数の値を1加算させることを意味します。
変数の加減算は、他にも以下のような簡易記述が可能です。

loop--;         // デクリメント演算子   (loop = loop - 1 と同じ意味)
loop += 1;      // 演算+代入を一度に行う(loop = loop + 1 と同じ)
loop += 2;      // 2つ加算する          (loop = loop + 2 と同じ)
loop -= 1;      // 1つ減算する          (loop = loop - 1 と同じ)
loop *= 2;      // 2倍する              (loop = loop * 2 と同じ)
loop /= 2;      // 2で割る              (loop = loop / 2 と同じ)



ですので、最初のサンプルにあった…

    for ( loop = 0; loop < 10; loop = loop+1 ) {
        printf( "%d回目の処理です\n", loop );
    }



は、以下のように書く事も出来ます(下の記述方法の方が一般的です)

    for ( loop = 0; loop < 10; loop++ ) {
        printf( "%d回目の処理です\n", loop );
    }







また、これまでの例ではカウンタ更新をloop = loop+1と1づつ加算させていましたが、それ以外の更新も可能です。

int main()
{
    int loop;
 
    /* 2づつカウントアップする */
    printf( "-- パターン3 --\n" );
    for ( loop = 0; loop < 10 ; loop += 2 ) {
        printf( "  %d回目の処理です\n", loop );
    }
 
    /* 1づつカウントダウンする */
    printf( "-- パターン4 --\n" );
    for ( loop = 5; loop > 0 ; loop-- ) {
        printf( "  %d回目の処理です\n", loop );
    }
 
    return 0;
}




実行結果

$ ./test01
-- パターン3 --
  0回目の処理です
  2回目の処理です
  4回目の処理です
  6回目の処理です
  8回目の処理です
-- パターン4 --
  5回目の処理です
  4回目の処理です
  3回目の処理です
  2回目の処理です
  1回目の処理です






for文では、終了条件を書き間違えると無限ループになってしまうので注意が必要です。
無限ループというのは、文字通り、ある処理を無限に実行し続けるプログラムで、強制終了を行わない限り永遠に動作し続けます。

例えば、以下のプログラムは終了条件が書かれていないので、いつまでまっても処理が終わらりません。

#include <stdio.h>
 
int main()
{
    int loop;
 
    // 無限ループ
    for ( loop = 1; ; loop++ ) {
        printf( "%d回目の処理です\n", loop ); 
    }
 
    return 0;
}



実行結果は以下の通りです。
プログラムを強制終了させる方法はOSによって異なりますが、通常はウィンドウ右上の”×”ボタンをクリックしたり、Ctrl-c(Ctrlキーを押しながらアルファベットのcを押す)ことで終了します。

1回目の処理です
2回目の処理です
3回目の処理です
...
13666回目の処理です
13667回目の処理です
13668回目の処理です
13669回目の処理です
13670回目の処理です
13671回目の処理です
13672回目の処理です
13673回目の処理です
...
^C                      <= ctrl-cを押して強制終了



先ほどの例では、終了条件を書かないことにより無限ループの構造を作っていました。
この終了条件は、if文と同様に”0以外”のときは真、”0″のときは偽、という判定ルールなので、以下のコードも無限ループになります。

    // 無限ループ(終了条件が0以外 -> 常に真になっている)
    for ( loop = 1; 1; loop++ ) {
        printf( "%d回目の処理です\n", loop ); 
    }





for文では指定された回数だけ処理を行う使用するので、終了条件はfor文の条件部(ここでいう条件部というのはfor( aaa; bbb; ccc )におけるbbb部の事です)に書くのが普通ですが、forの処理内に記述することも可能です。
繰り返しの途中でループを強制的に終わらせるにはbreak文を使用します。

以下の処理は先ほどと同様、for文の()内に終了条件が書かれていないので一見無限ループのように見えますが、for内にbreakがあるのでそこでループを抜けます。

int main()
{
    int loop;
 
    /* 無限ループと見せかけて... */
    for ( loop = 0; ; loop++ ) {
        printf( "%d回目の処理です\n", loop );
        printf( " ループ処理A\n" );
 
        if ( loop > 3 ) {
            /* ループを4回目で中断する */
            break;
        }
        printf( " ループ処理B\n" );
 
    }
 
    return 0;
}



実行結果

0回目の処理です
 ループ処理A
 ループ処理B
1回目の処理です
 ループ処理A
 ループ処理B
2回目の処理です
 ループ処理A
 ループ処理B
3回目の処理です
 ループ処理A
 ループ処理B
4回目の処理です
 ループ処理A


このようにループ内側でbreakを書くことで、途中でループを抜けることが出来ました。



また、continue文を書く事で、ループ処理を途中でキャンセルし、次のループに入らせることも出来ます。

int main()
{
    int loop;
 
    for ( loop = 0; loop < 10 ; loop++ ) {
        printf( "%d回目の処理です\n", loop );
 
        if ( loop % 2 == 0 ) {
            /* 偶数の場合は処理しない (loop%2)は2で割った余りを求める式) */
            continue;
        }
 
        printf( "    %dは奇数ですね!!\n", loop );
    }
 
    return 0;
}



実行結果

0回目の処理です
1回目の処理です
    1は奇数ですね!!
2回目の処理です
3回目の処理です
    3は奇数ですね!!
4回目の処理です
5回目の処理です
    5は奇数ですね!!
6回目の処理です
7回目の処理です
    7は奇数ですね!!
8回目の処理です
9回目の処理です
    9は奇数ですね!!


特定の条件に一致した時に、後続の処理を行いたくない場合に,continueを使用します。

[C言語入門]複数の分岐をswitchでシンプルに記述する

前回、if文による処理の分岐を説明しました。
C言語によるプログラムではifさえあればあらゆる分岐は記述できるのですが、場合によってはifの羅列では分かり辛くなってしまう場合も有ります。

例えば、以下のようにメニューを用意し入力値を判定させる場合、比較対象は常にdataであるにも関わらず、毎回”data ==”を書かなければなりません。無駄な表記があると書き間違いが発生しやすくなりますし、ソースコードも見づらくなります。

#include <stdio.h>
 
int main()
{
    int data;
 
    printf( "メニュー\n" );
    printf( "--------------------\n" );
    printf( "1:足し算\n" );
    printf( "2:引き算\n" );
    printf( "3:掛け算\n" );
    printf( "4:割り算\n" );
    printf( "\n" );
 
    printf( "メニューより番号を入力してください >" );
    scanf( "%d", &data );
    if ( data == 1 ) {
        printf( "足し算を行います\n" );
    } else if ( data == 2 ) {
        printf( "引き算を行います\n" );
    } else if ( data == 3 ) {
        printf( "掛け算を行います\n" );
    } else if ( data == 4 ) {
        printf( "割り算を行います\n" );
    } else {
        printf( "入力値が不正です\n" );
    }
}




このような場合は、switch文を使うとすっきりします。
以下のコードは、先ほどのプログラムをswitchで書き直したものです。

#include <stdio.h>
 
int main()
{
    int data;
 
    printf( "メニュー\n" );
    printf( "--------------------\n" );
    printf( "1:足し算\n" );
    printf( "2:引き算\n" );
    printf( "3:掛け算\n" );
    printf( "4:割り算\n" );
    printf( "\n" );
 
    printf( "メニューより番号を入力してください >" );
    scanf( "%d", &data );
 
    switch( data ) {
        case 1:
            printf( "足し算を行います\n" );
            break;
        case 2:
            printf( "引き算を行います\n" );
            break;
        case 3:
            printf( "掛け算を行います\n" );
            break;
        case 4:
            printf( "割り算を行います\n" );
            break;
        default:
            printf( "入力値が不正です\n" );
            break;
    }
}



switch文を使う事で、比較対象の情報が変数”data”である事と、その値が1,2,3,4だった時に処理がある事が分かりやすくなりました。

このプログラムの実行結果は、以下のようになります。

$ gcc -Wall -o test03 test03.c
 
$ ./test03
メニュー
--------------------
1:足し算
2:引き算
3:掛け算
4:割り算
 
メニューより番号を入力してください >1
足し算を行います
 
 
$ ./test03
メニュー
--------------------
1:足し算
2:引き算
3:掛け算
4:割り算
 
メニューより番号を入力してください >2
引き算を行います
 
 
$ ./test03
メニュー
--------------------
1:足し算
2:引き算
3:掛け算
4:割り算
 
メニューより番号を入力してください >4
割り算を行います
 
 
$ ./test03
メニュー
--------------------
1:足し算
2:引き算
3:掛け算
4:割り算
 
メニューより番号を入力してください >5
入力値が不正です
 
 
$ ./test03
メニュー
--------------------
1:足し算
2:引き算
3:掛け算
4:割り算
 
メニューより番号を入力してください >-1
入力値が不正です





今回登場したswitch文ですが、構文は以下の通りです。

switch( 条件 ) {
    case 値1:
        処理内容1
    case 値2:
        処理内容2
    ...
 
    default:
        どのcaseにも一致しない場合の処理
}




switch文の振る舞いについては、以下のルールで処理が実行されます。

Step1
    条件に書かれた式を評価します。
 
Step2
    評価値を元に,各caseの後に書かれた値と一致するcase要素を探します。
 
    等しいものがあった場合、次実行する命令をその場所にジャンプします。
    等しいものがなく,default:があった場合、次実行する命令をdefaultにジャンプします。
    等しいものがなく,default:も無い場合、次実行する命令は,switch後の処理にジャンプします。
 
Step3
    break文が現れるまで、switch文の中の処理を順に処理します。




caseの後にある”値”には、定数のみ記述できます。
以下にcaseとして書ける記述と書けない記述の例を示します。

int x = 2;  // 変数を宣言
 
switch( data ) {
    case 1:                    /* OK: 定数の指定が行える */
        printf( "case1です" );
        break;
 
    case x:                    /* NG: 変数は指定できない !! */
        printf( "case2です" );
        break;
 
    case 1+2:                  /* OK: 式の結果はコンパイル時に確定するので大丈夫 */
        printf( "case3です" );
        break;
 
    case '9':                  /* OK: 文字定数は数値とみなされるのでOK         */
                               /*     '9'はアスキーコードで0x39(10進数だと57)  */
                               /*     なのでcase 57:と書いたのと同じ意味になる */
        printf( "case9です" );
        break;
 
    default:
        //どのcaseにも一致しない場合の処理
        break;
}




まだ説明していない内容ですが、const定数やdefine値をcase値として使用出来るかも説明しておきます。
(ここは、意味が分からない人は読み飛ばしてもらっても結構です)

#define PTN1 1                   /* defineによる定数の定義 */
 
const int PTN2 = 2;              /* constによる変更不可値の定義 */
 
switch( data ) {
    case PTN1:                   /* OK: 定数の指定が行える */
        printf( "case1です" );
        break;
 
    case PTN2:                   /* NG: 変数は指定できない */
        printf( "case2です" );
        break;
 
    default:
        printf( "defaultです\n" );
}





指定された値に応じて処理を分岐させたいので、通常は各case句の最後にbreakが入る事になります。
もし、breakが書かれていないと、次のcase句の処理が実行され行きます。

例えば以下のプログラムを実行すると…

#include <stdio.h>
 
int main()
{
    int data = 2;
 
    switch( data ) {
        case 1:
            printf( "足し算を行います\n" );
            break;
        case 2:
            printf( "引き算を行います\n" );    /* breakを書き忘れた!! */
 
        case 3:
            printf( "掛け算を行います\n" );
            break;
        case 4:
            printf( "割り算を行います\n" );
            break;
        default:
            printf( "入力が不正です\n" );
            break;
    }
 
    return 0;
}




実行結果は以下のようになってしまいます。

$ gcc -Wall -o test03 test03.c
 
$ ./test03
引き算を行います
掛け算を行います


breakの書き忘れによって、プログラムが意図しない動作をするのは初心者によくあるミスなので注意が必要です。


一方、以下のようにあえてbreakを書かずに処理を行う場合もあります。

例1:0、(0以外の)偶数、奇数を判定する

switch (data ) {
    case 0:
        printf( "値が0です\n" );
        break;
 
    case 1:
    case 3:
    case 5:
    case 7:
    case 9:
        printf( "奇数です\n" );
        break;
 
    case 2:
    case 4:
    case 6:
    case 8:
        printf( "偶数です\n" );
        break;
}



例2:プログラムレベルに応じた学習メニューを提示する */

int level;
printf( "C言語の知識レベルを1~3で入力して下さい" );
scanf( "%d," &level );
 
switch ( level ) {
    case 1:
        printf( "入門編のテキストを勉強してください\n" );
        /* FALLTHROUGH */
    case 2:
        printf( "中級編のテキストを勉強してください\n" );
        /* FALLTHROUGH */
    case 3:
        printf( "上級編のテキストを勉強してください\n" );
        break;
    default:
        printf( "入力値が不正です\n" );
        break;
}


後者の例のように、ある処理を行った後、引き続き次の処理を行いたい場合にbreakを省略すると、後から見た人が、このコードはbreakを意図的に省略したのか、それとも書き忘れたのかを、判断できなくなってしまいます。この為、/*FALLTHROUGH*/(下に処理が流れていく)や、/*nobreak*/というコメント等をあえて書いておいてあげたほうが親切です。
企業によっては、この様なコメントを書く事を必須とする社内ルールを設けている場合も多いです。



また、どのcaseにも合致しない場合default句の処理が走ると説明しましたが、defaultのつづりが間違っていてもコンパイルエラーにはなりません。これも気づきにくいミスの1つです。

#include <stdio.h>
 
int main()
{
    int data = 99;  /* defaultに入る事を想定 */
 
    printf( "処理開始\n" );
    switch( data ) {
        case 1:
            printf( "1が指定されました\n" );
            break;
        case 2:
            printf( "2が指定されました\n" );
            break;
        defualt:                            /* defaultのつづりを間違えた!! */
            printf( "入力が不正です\n" );
            break;
    }
    printf( "処理終了\n" );
 
    return 0;
}



実行結果

$ gcc -o test03 test03.c    <= コンパイルは正常に行われる
 
$ ./test03                  <= 実行しても,"入力が不正です"のメッセージが出ない
処理開始
処理終了




また、switch文のdefaultはC言語の文法的には省略可能ですが、習慣として常にdefault処理は書いておいた方が良いです。
仮にプログラムの作り上、defaultに入る可能性がありえない場合でも、default句内でエラーメッセージを表示させておけば、バグがあった場合に気付くのが早くなります。

[gcc]前置インクリメントと後置インクリメントで生成されるアセンブラコードの違い

今回のお題:
以下の文脈で、前置インクリメントと後置インクリメントを行った。
生成されるアセンブラコードは、iとjでどう変わるのか?

#include <stdio.h>
 
int main()
{
    int i = 0;
    int j = 0;
 
    for ( i = 0, j = 0; i < 5 ; ) {
        printf( "ptn2 i = %d, j = %d\n", ++i, j++ );
    }
 
    return 0;
}






使用したコンパイルオプションは以下の通りです。

gcc -Wall -S -o incTest incTest.c




生成されたコード(incTest.s)は以下の通りになりました。
長くなるので必要部のみ抜粋します。(頭の数字は行番号です)

 38     movl    $0, 28(%esp)
 39     movl    $0, 24(%esp)
 40     jmp L4
 41 L5:
 42     movl    24(%esp), %eax
 43     addl    $1, 24(%esp)
 44     addl    $1, 28(%esp)
 45     movl    %eax, 8(%esp)
 46     movl    28(%esp), %eax
 47     movl    %eax, 4(%esp)
 48     movl    $LC1, (%esp)
 49     call    _printf
 50 L4:
 51     cmpl    $4, 28(%esp)
 52     jle L5




このコードを解読してます。
解読結果はコメントの形で追加しました。

 38     movl    $0, 28(%esp)	; i = 0
 39     movl    $0, 24(%esp)	; j = 0
 40     jmp L4
 
 41 L5:
 42     movl    24(%esp), %eax	; $eax = j
 43     addl    $1, 24(%esp)	; j = j+1
 44     addl    $1, 28(%esp)    ; i = i+1
 
 45     movl    %eax, 8(%esp)   ; esp[8] = eax
 
 46     movl    28(%esp), %eax  ; eax    = i
 47     movl    %eax, 4(%esp)   ; esp[4] = eax
 
 48     movl    $LC1, (%esp)    ; esp[0] = strPtr
 49     call    _printf			; printfを呼び出し
 50 L4:
 51     cmpl    $4, 28(%esp)	if ( i <= 4 ) { goto L5; }
 52     jle L5
 
 53     movl    $0, %eax
 54     leave



見てみると、上記コードの場合はprintfの呼び出し前にi,jとも加算してしまっているようです。
加算前の値をeaxレジスタに退避したうえで,その値をスタックに乗せてprintfをコールしてました。

[C言語]printfの後の入力待ちで入力バッファがクリアされる理由

C言語の入門者向けに書かれた書籍を見ると、以下のようなプログラムが良く有ります。

#include<stdio.h>
int main()
{
    int value;
    printf( "数字を入力して下さい:" );
    scanf( "%d", &value );
 
    printf( "入力値は%dです\n", value );
    return 0;
}



このコード、今まで特に気にしてこなかったのですが、良く考えてみるとちょっと疑問に思うところが出てきました。

printf()は入力をバッファリングしているので、指定した文字列が直ぐに表示されるとは限りません。
そうすると、scanfの前にprintfの文字が出力されるとは限らず,厳密には以下のコードのようにすべきでは?
というのが疑問点です。

    printf( "数字を入力して下さい:" );
    fflush( stdout );                // バッファのフラッシュが必要では?
    scanf( "%d", &value );



ちなみに、上記のコードをlinux+gccでコンパイルしてgdbでデバッグしてみると、printfでは文字が表示されず、次のscanfで出力されている事は確認済みです(Cプログラマにとっては常識レベルの事ですが,念のため)。

以下、確認結果です。

$ gcc -O0 -g -o inputcheck inputcheck.c
$ gdb inputcheck
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
Reading symbols from /home/user1/work/inputcheck...done.
(gdb) list
1   #include<stdio.h>
2   int main()
3   {
4       int value;
5       printf( "数字を入力して下さい:" );
6       scanf( "%d", &value );
7   
8       printf( "入力値は%dです\n", value );
9       return 0;
10  }
(gdb) break 5
Breakpoint 1 at 0x804843d: file inputcheck.c, line 5.
(gdb) start
Temporary breakpoint 2 at 0x804843d: file inputcheck.c, line 5.
Starting program: /home/user1/work/inputcheck 
 
Breakpoint 1, main () at inputcheck.c:5
5       printf( "数字を入力して下さい:" );
(gdb) next                                                 <= ここで表示されずに...
6       scanf( "%d", &value );
(gdb) next                                                 <= scanf()のタイミングで出る
数字を入力して下さい:10
8       printf( "入力値は%dです\n", value );
(gdb) continue
Continuing.
入力値は10です
[Inferior 1 (process 7573) exited normally]
(gdb)



この辺のところはかなり細かな話で、市販の書籍だと適当な事が書いてある危険があるので、規格書であるJIS Cで確認してみる事にします(本当はANSI Cでチェックしたかったのですが、手元に無いので)。

以下、関連する箇所をC言語の規格書であるJIS X3010より引用します。
JIS規格のドキュメントはhttp://www.jisc.go.jp/app/JPS/JPSO0020.htmlより閲覧できます。

5.1.2.3 プログラムの実行
*中略*
対話型装置に対する入出力動作は7.19.3で規定する通りに行う。この要求は、入力要求メッセージ
が実際にプログラムの入力待ち以前に現れることを確実にするため、バッファリングされない出力
又は、行バッファリングされた出力ができる限り早く現れることを意図する。



“…意図する”という所が知りたい情報です。
入出力動作は7.19.3で規定って事なので、そちらも見てみます。

7.19.3 ファイル
*中略*
ストリームをバッファリングしていない(unbuffered)場合,
    文字が入力もとから又は出力先へ可能な限りすぐに表れることを意図する。
    そうでない場合、文字を貯めているホスト環境へまたはホスト環境からブロック単位で転送してもよい。
 
ストリームを完全バッファリングしている(fully buffered)場合,
    バッファが一杯になった時点で,ホスト環境へまたはホスト環境から文字をブロック単位することを意図する。
 
ストリームを行バッファリングしている(line buffered)場合、
    改行文字に達した時点で、ホスト環境へまたはホスト環境から文字をブロック単位することを意図する。
 
 
さらに、次に掲げる三つの場合、ホスト環境へ文字をブロック単位で転送することを意図する。
 -バッファがいっぱいになった場合
 -バッファリングしていないストリームに対して入力要求があった場合
 -行バッファリングしているストリームに対して、ホスト環境からの文字転送を必要とする入力要求があった場合
 
これらの特性のサポートは処理系定義とし、setbuf関数およびsetvbuf関数の影響を受けてもよい。



というわけで、JIS C的には、scanfのタイミングでstdoutの出力バッファがクリアされるのは、JIS X3010の7.19.3の以下の記述で規定されていました。

さらに、次に掲げる三つの場合、ホスト環境へ文字をブロック単位で転送することを意図する。
 -行バッファリングしているストリームに対して、ホスト環境からの文字転送を必要とする入力要求があった場合



ただ、その後に”これらの特性のサポートは処理系定義”という記述もあります。
なので、厳密に言えば 事前にfflush()した方が確実なのかもしれません。

入門ANSI‐C

gdbによるデバッグのチュートリアル その2

前回の続きです。
gdbによるデバッグのチュートリアル その1

今回は、レジスタ値の確認方法と、Intelアーキテクチャにおけるスタックの使用方法をみていきます。



レジスタ一覧はinfo registerで確認できます。

(gdb) info register
eax            0x14	20
ecx            0xbffff924	-1073743580
edx            0xbffff8b4	-1073743692
ebx            0x2a8ff4	2789364
esp            0xbffff860	0xbffff860
ebp            0xbffff888	0xbffff888
esi            0x0	0
edi            0x0	0
eip            0x80483ff	0x80483ff <main+27>
eflags         0x200216	[ PF AF IF ID ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51



intelのCPUだとeipレジスタがプログラムカウンタ(PC)になります。
現在、0x80483ff番地の命令で止まっている事が分かるので、アドレス指定でlistコマンドを実行すると、11行目にいることが分かります。

(gdb) list *0x80483ff
0x80483ff is in main (test01.c:11).
6	{
7	
8		int a = 10;
9		int b = a  * 2;
10	
11		int ans = sum( a, b );
12		printf( "%d\n", ans );
13		return 0;
14	}
15



もう一回stepを行うとsum()関数の中に入ります。
ちなみに、nextコマンドを打つと関数の中に入らず、次の行に行きます。(VisualStudioのステップオーバーと同じ機能です)

(gdb) step
sum (a=10, b=20) at test01.c:18
18		int result = a + b;




backtraceで、関数のコールスタックを確認します。

(gdb) backtrace
#0  sum (a=10, b=20) at test01.c:18
#1  0x08048413 in main () at test01.c:11



info registerコマンドで再度レジスタ値を見ます。
レジスタ名を指定することで、該当のものだけが表示されます。
intelのCPUの場合、FP(frame pointer)はebpレジスタに保持されています。
また、SP(stack pointer)はespです。

(gdb) info register eip ebp esp
eip            0x8048439	0x8048439 <sum+6>
ebp            0xbffff858	0xbffff858
esp            0xbffff848	0xbffff848



SPで指し示すアドレスがスタックの一番上で、FPが現在実行中の関数の情報がある底になります。
スタックは大きな番地から若い方に進んでいくので、SP 上記の例だとsum()関数が使っているスタックは0xbffff858-0xbffff848で、0x10ぶんのサイズとなります。

xコマンドでメモリの値をダンプできます。
アドレスは16進で直接指定するほかに、レジスタを指定することも可能です。

(gdb) x 0xbffff848
0xbffff848:	0x00000001
 
(gdb) x $esp
0xbffff848:	0x00000001




xの後に”/数字”で、指定サイズ分のメモリダンプが出来ます。

(gdb) x/20 $esp
0xbffff848:	0x00000001	0x080482dd	0x0012f918	0x0804824c
0xbffff858:	0xbffff888	0x08048413	0x0000000a	0x00000014
0xbffff868:	0x00163bdb	0x002a9324	0x002a8ff4	0x0000000a
0xbffff878:	0x00000014	0x002a8ff4	0x08048450	0x00000000
0xbffff888:	0x00000000	0x0014a113	0x00000001	0xbffff924





説明のために、4byteづつ行を変えて整形します。
これからスタック領域に積まれた情報を解析していきます。

0xbffff848: 0x00000001  		<= $esp
            0x080482dd  
            0x0012f918  
            0x0804824c  
0xbffff858: 0xbffff888  		<= $ebp 
            0x08048413  
            0x0000000a  sum関数 引数aの値
            0x00000014  sum関数 引数bの値
0xbffff868: 0x00163bdb  
            0x002a9324  
            0x002a8ff4  
            0x0000000a  
0xbffff878: 0x00000014  
            0x002a8ff4  
            0x08048450  
            0x00000000  
0xbffff888: 0x00000000  
            0x0014a113  
            0x00000001  
            0xbffff924



まず、sum()に渡したパラメータa,bはそれぞれ10,20でしたが、これは$ebp+4, $ebp+8の場所に入っています。

次にsum関数のリターンアドレスがその次に有ります。
backtraceを見て分かるように、戻り先は0x08048413番地です。

(gdb) bt
#0  sum (a=10, b=20) at test01.c:18
#1  0x08048413 in main () at test01.c:11




その次の0xbffff888は、上位関数であるmainのFPです。
フレームポインタは1個しかないので、関数がコールされるたびに1つ前の関数のFPを、このようにスタックへ乗せていきます。


次の0x0804824cは、sum関数内の自動変数であるint resultの格納場所です。
これは、print命令(p)でresultのアドレス/値をチェックすると分かります。
print命令は後ろに”/x”をつけると16進表記になります。

(gdb) p result
$3 = 134513228
 
(gdb) p/x result
$4 = 0x804824c
 
(gdb) p/x &result
$5 = 0xbffff854



さらにその上にある3word文の領域には、それぞれ値0x0012f918,0x080482dd,0x00000001が入っていますが、これはsum関数が使用するワークエリアです。


というわけで分かった情報を書き加えると、以下の構造になっています。

0xbffff848: 0x00000001  sum関数 作業領域		<= $esp
            0x080482dd  sum関数 作業領域		   $esp+4
            0x0012f918  sum関数 作業領域		   $esp+8
            0x0804824c  sum関数 変数result		   $esp+12
0xbffff858: 0xbffff888  main関数のFPアドレス	<= $ebp 
            0x08048413  sum()のリターンアドレス
            0x0000000a  sum関数 引数aの値
            0x00000014  sum関数 引数bの値
0xbffff868: 0x00163bdb  
            0x002a9324  
            0x002a8ff4  
            0x0000000a  
0xbffff878: 0x00000014  
            0x002a8ff4  
            0x08048450  
            0x00000000  
0xbffff888: 0x00000000  main関数のFP位置($ebp)
            0x0014a113  
            0x00000001  
            0xbffff924



試しに、もう1命令実行させてresultに値を代入すると、予想通り$esp+12の値が30(0x1e)になりました。

(gdb) next
19	    return result;
 
(gdb) x/4 $esp
0xbffff848:	0x00000001	0x080482dd	0x0012f918	0x0000001e <-




main関数から戻ると、SPである$EIPレジスタの値が、0xbffff888に変わっています。

(gdb) next
20	}
 
(gdb) next
main () at test01.c:12
12	    printf( "%d\n", ans );
 
(gdb) info register eip ebp esp
eip            0x8048417	0x8048417 <main+51>
ebp            0xbffff888	0xbffff888
esp            0xbffff860	0xbffff860




continueでプログラムの最後まで一度に実行させてしまいます。
途中で,printfの出力の”30″が表示されています。

(gdb) continue
Continuing.
30
[Inferior 1 (process 26593) exited normally]
(gdb)


gdbによるデバッグのチュートリアル その1

gccで作ったC言語のプログラムを、gdbコマンドを使用してデバッグしてみます。

今回の実験環境は以下の通り。
linux環境でテストしてますが、cygwinでも問題なく動作するはずです…

$ gcc --version
gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1
 
$ gdb --version
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
 
$ uname -a
Linux linuxmint 3.0.0-12-generic #20-Ubuntu SMP Fri Oct 7 14:50:42 UTC 2011 i686 i686 i386 GNU/Linux



今回実験で使うソースコードです。
単なる足し算プログラムです。

/* test01.c */
#include <stdio.h>
 
int sum( int a, int b );
 
int main()
{
 
    int a = 10; 
    int b = a  * 2;
 
    int ans = sum( a, b );
    printf( "%d\n", ans );
    return 0;
}
 
int sum( int a, int b )
{
    int result = a + b;
    return result;
}



これをコンパイルします。
コンパイル時に-gでデバッグ情報を埋め込むと共に、-O0で最適化を抑制します。
また、-Sオプションでアセンブラのコードも出力しておきます。

$ gcc -g -O0 -o test01 test01.c
 
$ gcc -S -O0 test01.c
 
$ ls
test01  test01.c  test01.s



出来上がったtest01.sはこんな感じ。

        .file   "test01.c"
        .section        .rodata
.LC0:
        .string "%d\n"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        andl    $-16, %esp
        subl    $32, %esp
        movl    $10, 20(%esp)
        movl    20(%esp), %eax
        addl    %eax, %eax
        movl    %eax, 24(%esp)
        movl    24(%esp), %eax
        movl    %eax, 4(%esp)
        movl    20(%esp), %eax
        movl    %eax, (%esp)
        call    sum
        movl    %eax, 28(%esp)
        movl    $.LC0, %eax
        movl    28(%esp), %edx
        movl    %edx, 4(%esp)
        movl    %eax, (%esp)
        call    printf
        movl    $0, %eax
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .globl  sum
        .type   sum, @function
sum:
.LFB1:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $16, %esp
        movl    12(%ebp), %eax
        movl    8(%ebp), %edx
        addl    %edx, %eax
        movl    %eax, -4(%ebp)
        movl    -4(%ebp), %eax
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE1:
        .size   sum, .-sum
        .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
        .section        .note.GNU-stack,"",@progbits



作ったプログラムをgdbに読み込ませます。

$ gdb ./test01
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/user/work/test01...done.



コンパイル時に-gオプションを付け忘れると、最後の行が以下の出力になります。
デバッグシンボルが無いよ。と怒られてます。

Reading symbols from /home/user/work/test01...(no debugging symbols found)...done.



listコマンドでソースを確認します。
オプションをつけると、行番号指定や関数名指定で表示する事ができます。

(gdb) list 
1   #include <stdio.h>
2   
3   int sum( int a, int b );
4   
5   int main()
6   {
7   
8       int a = 10;
9       int b = a  * 2;
10  
 
(gdb) list 5, 12
5   int main()
6   {
7   
8       int a = 10;
9       int b = a  * 2;
10  
11      int ans = sum( a, b );
12      printf( "%d\n", ans );
 
(gdb) list sum
12      printf( "%d\n", ans );
13      return 0;
14  }
15  
16  int sum( int a, int b ) 
17  {
18      int result = a + b;
19      return result;
20  }



bコマンド(break)で、ブレークポイントをセットできます。

(gdb) b 8
Breakpoint 1 at 0x80483ed: file test01.c, line 8.



startでプログラムの実行開始です。
ブレークポイントを設定していたので、8行目で止まりました。
nextコマンドでもう1行だけ進めます。

(gdb) start
Temporary breakpoint 2 at 0x80483ed: file test01.c, line 8.
Starting program: /home/user1/work/test01 
 
Breakpoint 1, main () at test01.c:8
8       int a = 10;
(gdb) next
9       int b = a  * 2;



info bで、ブレークポイント一覧が確認できます。

(gdb) info b
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x080483ed in main at test01.c:8
    breakpoint already hit 1 time



printで変数の中身を表示します。
変数bは、まだ初期化されてないので不定値が入ってます。

(gdb) print a
$2 = 10
(gdb) print b
$3 = 134513753



もう一行進めたらセットされました。

(gdb) next
11      int ans = sum( a, b );
(gdb) print b
$4 = 20



変数のアドレスも確認できます。

(gdb) print &a
$5 = (int *) 0xbffff874
(gdb) print &b
$6 = (int *) 0xbffff878



長くなってきたので、続きは次の記事へ…
次記事 -> gdbによるデバッグのチュートリアル その2
続きはでは、レジスタとスタック領域の使われ方を確認します。

[C言語入門]処理の内容をif文で分岐させる

前回までのサンプルで確認したように、C言語のプログラムは基本的に上から書いた順に関数が実行されていく形で動作します。

一連の作業を順に行うだけならこれで構わないのですが、プログラムによっては状況に応じて処理を分岐したい場合も出てきます。

このような場合は、if文による処理分岐を行います。


というわけで、今回のサンプルはこちらです。
点数を入力し、60点以上なら合格、60点未満なら不合格と表示するプログラムです。

#include <stdio.h>
 
int main()
{
    int score;
 
    printf( "点数を入力してください>" );
    scanf( "%d", &score );
 
    if ( score >= 60 ) {
        printf( "合格です\n" );
    } else {
        printf( "不合格です\n" );
    }
}



実行すると、以下のような結果になります。
入力した値に応じて表示されるメッセージが変わっています。

$ gcc -o iftest.exe iftest.c
 
$ ./iftest.exe
点数を入力してください>60
合格です
 
$ ./iftest.exe
点数を入力してください>50
不合格です
 
$ ./iftest.exe
点数を入力してください>63
合格です






if文の構文

if文は以下の構成をとります。

if ( 条件式 ) {
    条件が成立した場合の処理
} else {
    条件が成立しない場合の処理
}



条件が成立しない場合の処理がない場合は、elseの部分は省略する事も可能です。

if ( 条件式 ) {
    条件が成立した場合の処理
}



さらに条件が成立した場合の処理が1命令しかない場合は、括弧も省略する事も出来ます。

if ( 条件式 )
    条件が成立した場合の処理




プログラム的には問題なく動作する書き方ですが、この記法は人が見たときに分かり辛くなる為、1命令しかない場合でも括弧を書くほうが望ましいです。
例えば、以下のプログラムは期待したように動きますが…

/* 100点のときに満点と表示する */
if ( score == 100 )
    printf( "満点です\n" );



これはNGです。

if ( score == 100 )
    printf( "満点です\n" );
    printf( "おめでとうございます\n" );



括弧が無いときにifが掛かる処理は1文だけなので、上記のコードは以下の処理と等価です。

if ( score == 100 ) {
    printf( "満点です\n" );
}
printf( "おめでとうございます\n" );


これでは、100点以外の人にも”おめでとうございます”のメッセージが表示されてしまいます。



elseの後には、さらにifを連ねる事も可能です

if ( score > 60 ) {
    printf( "残念です\n" );
} else if ( score > 80 ) {
    printf( "良く出来ました\n" );
} else {
    printf( "たいへん良く出来ました\n" );
}



前述した”if内の命令が1文のときは括弧を省略できる”というルールはelse側にも適用されるので、上記コードは実際には以下の処理となります。

/* else側で省略された括弧を明示する */
if ( score > 60 ) {
    printf( "残念です\n" );
} else {
    if ( score > 80 ) {
        printf( "良く出来ました\n" );
    } else {
        printf( "たいへん良く出来ました\n" );
    }
}



上記2つは、どちらの書き方でも同じ振る舞いになりますが、多分岐である事を明示したい場合は、前者の記法をすることが多いです。



条件式として指定する値

前述のif文には条件式として不等号による判断式でした。
C言語で判断式を書いた場合その式の評価結果は、以下の値を取ります。

条件が成立しない場合 : 0と評価される
条件が成立する場合   : 0以外と評価される



こんな事を急に書くと訳が分からなくなりそうなので、一旦確認用のプログラムを作成してみます。

#include <stdio.h>
 
int main()
{
    int score;
    int result;
 
    printf( "点数を入力してください>" );
    scanf( "%d", &score );
 
 
    result = ( score > 60 );  /* 条件式の判断結果を変数に代入する */
    printf( "resultの値は%dです\n", result );
}



実行結果は以下のようになりました。

$ gcc -o iftest iftest.c
 
$ ./iftest
点数を入力してください>50
resultの値は0です
 
$ ./iftest
点数を入力してください>62
resultの値は1です
 
$ ./iftest
点数を入力してください>100
resultの値は1です



というわけで確かに、条件が成立しない場合は0、成立する場合は0以外(上記例では1)がセットされました。
ここで、”0以外と評価される”ですがこれはC言語の規格上の定義(ANSI C)になります。
実際のところは大抵のコンパイラで1がセットされる事が多いのですが、1が返される事を期待したプログラムを作成するのはNGです。
コンパイラによっては、2を返したとしてもC言語の規格にはマッチしているので、移植性の悪いプログラムになってしまいます。



…というわけで、条件式の評価結果を確認してみました。
で、今回の話題であるif文ですが、これも上記のルールを踏襲しており、真偽値を、”0の時は偽/0以外の時は真”とみなします。
なので、if文の条件判定は、以下の振る舞いとなります。

if ( 0 ) {
    /* この処理は走らない */
} else {
    /* この処理が実行される */
}
 
if ( 1 ) {
    /* この処理が実行される */
} else {
    /* この処理は走らない */
}
 
if ( 2 ) {
    /* この処理が実行される */
} else {
    /* この処理は走らない */
}





よく使われる条件式


C言語のifによる条件分岐では、関係演算子を使用することが多いです。
よく使用する関係演算子は以下の通りです

演算子 使用例   意味
==     a == b   aとbが等しいとき真
!=     a != b   aとbが等しくないとき真
>=     a >= b   aがb以上のとき真
<=     a <= b   aがb以下のとき真
>      a > b    aがbより大きいとき真
<      a < b    aがbより小さいとき真



また、複数の条件を組み合わせる事もできます。

&&  論理積
||  論理和
 
!   否定



組み合わせは、例えば以下のように記述できます。

if ( a % 3 == 0 || a % 5 == 0 ) {
    // 3の倍数か5の倍数の時に処理を行う
}





条件式の書き間違いに注意


条件比較で==とすべきところを間違って=にしてしまうのはよくあるミスです。
=にしてしまうと、代入を行った上で代入値が0かどうかの判定を行う事になり、期待した動作にならないので気をつけてください。

[gcc]if文と3項演算子では,出力されるコードが異なる

gccのアセンブラ出力を見ていて気になったのでメモ。
C言語ではif文を使わなくても、3項演算子を使用して条件分岐を “( a > b ) ? a : b”のような感じで書く事が出来ますが、両者の書き方で生成されるコードが異なるようでした。


3項演算子を使用した場合(test01.c)

#include <stdio.h>
 
int sum( int a, int b );
 
int main()
{
    int a = 10;
    int b = 10;
    int result;
    result = ( a > b ) ? a : b;
 
//  printf( "largest value is %d\n", result );
    return 0;
}




普通にifを使用した場合(test02.c)

#include <stdio.h>
 
int sum( int a, int b );
 
int main()
{
    int a = 10;
    int b = 10;
    int result;
    if ( a > b ) {
        result = a;
    } else {
        result = b;
    }
 
//  printf( "largest value is %d\n", result );
    return 0;
}



それぞれ、以下のコマンドでアセンブラ出力を行います

gcc -S test01.c
gcc -S test02.c







出力結果を確認します。

3項演算子を使用した場合

    .file   "test01.c"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $16, %esp
    movl    $10, -12(%ebp)
    movl    $10, -8(%ebp)
    movl    -12(%ebp), %eax
    cmpl    %eax, -8(%ebp)
    cmovge  -8(%ebp), %eax
    movl    %eax, -4(%ebp)
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
    .section    .note.GNU-stack,"",@progbits



ifの場合

    .file   "test02.c"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $16, %esp
    movl    $10, -12(%ebp)
    movl    $10, -8(%ebp)
    movl    -12(%ebp), %eax
    cmpl    -8(%ebp), %eax
    jle .L2
    movl    -12(%ebp), %eax
    movl    %eax, -4(%ebp)
    jmp .L3
.L2:
    movl    -8(%ebp), %eax
    movl    %eax, -4(%ebp)
.L3:
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
    .section    .note.GNU-stack,"",@progbits




両者とも2つの変数値に、movl命令でアクセスし、cmplで比較しています。

    movl    $10, -12(%ebp)
    movl    $10, -8(%ebp)
    movl    -12(%ebp), %eax
    cmpl    -8(%ebp), %eax



その後、3項演算子はcmovgeという命令で処理しているのに対して…

    cmovge  -8(%ebp), %eax
    movl    %eax, -4(%ebp)
    movl    $0, %eax



ifではジャンプ命令で普通に分岐させています

    jle .L2
    movl    -12(%ebp), %eax
    movl    %eax, -4(%ebp)
    jmp .L3
.L2:
    movl    -8(%ebp), %eax
    movl    %eax, -4(%ebp)
.L3:
    movl    $0, %eax
    leave





cmovge命令が何かと思って調べてみたら、msdnから確認できました。
説明によると、条件付での代入な様です。
http://msdn.microsoft.com/en-us/library/aa226718%28v=vs.60%29.aspx

Syntax
cmovge $s_reg1, $s_reg2, $d_reg
cmovge $d_reg/$s_reg1, $s_reg2
cmovge $s_reg1, val_immed, $d_reg
cmovge $d_reg/$s_reg1, val_immed
 
Description
 
Move if Greater Than or Equal to Zero moves the contents of $s_reg2 or 
the immediate value to the destination register if the contents of 
$s_reg1 is greater than or equal to zero.







もちろんintelのドキュメントにも記載されています。
http://download.intel.com/jp/developer/jpdoc/EM64T_VOL1_30083402_i.pdf

CMOVcc - Conditional Move命令
-----------------------------
IA-32e モードでの操作
 
temp <-- DEST
IF condition TRUE
    THEN
        DEST <-- SRC
    ELSIF (osize = 32)
        DEST <-- temp AND 0x00000000FFFFFFFF
        FI;
    ELSE
        DEST <-- temp
        FI;
    FI;


こっちのほうが、プログラムっぽく記述されているので分かりやすい。
まさに条件付の代入でした。



速度的には、おそらく3項演算子版のほうが良いだろうと思ってベンチマークを取ってみたのですが、なぜかifバージョンのほうが若干速い結果でした。
なぜだろう….

ちなみに当たり前の事ですが、生成されるコードはコンパイラやターゲットCPUに依存するので、”両者で必ず異なるコードが生成される”という意味ではないです。

ラッチとフリップフロップの違い

論理回路では、入力信号の状態を保持したい場合がある。
このような場合の最もプリミティブな機構として、ラッチやフリップフロップがある。


RSラッチ

1ビットの状態保持を行う事ができる。
入力状態が直ぐに出力へ反映される。
RとSを同時にアサート状態にすることは出来ない。


Dラッチ

RSラッチと基本的に同じで1ビットの状態保持を行う事ができるが、ゲートピンがEnableの時のみ出力が変更される。


Dフリップフロップ

CK信号が立ち上がりのタイミングの入力を保持する。
同期式回路を組むときに使う。
現実は電子的な遅延があるので、実際はCK信号のちょっと前(セットアップ時間)から入力を安定させておき、ちょっと後(ホールド時間)まで保持する必要がある。

JKフリップフロップ

RSラッチに似ているが、CK信号によるエッジトリガなのと、JとKのピンを両方アサート状態しても良い点が異なる。
両方アサートにすると、出力が反転する



立ち上がりのエッジで動作を規定するものを”エッジトリガ”、状態で規定するものを”レベルトリガ”と呼ぶ。一般的にフリップフロップはエッジトリガ、ラッチはレベルトリガだが、場合によってはレベルとリガを含めてフリップフロップと呼ぶ場合もある。


信号が有効な状態をアサート、無効な状態をネゲートと呼ぶ。
正論理の場合はアサート=1だが、負論理だとアサート=0になる。

[C言語入門]変数が保持できる値の範囲について

変数の宣言

今回のサンプルでは、”int result”ということで、整数型の変数resultを定義しました。
変数として宣言できるのは,int以外にも以下のデータ型があります。

char     主に文字を格納するのに使用
int      主に整数を格納するのに使用
float    主に大きくない実数値を格納するのに使用
double   主に大きな実数値を格納するのに使用



また、intに関してはshort,longの接頭語を付けることもできます。
(コンパイラによっては、long double型の宣言も可能です)

short int
long int
long long int



上記の場合intは省略可能なので、以下の書き方をする事もできます。

short
long
long long




さらに、これらの型の接頭語にunsignedを付けることもできます。

unsigned char
unsigned int




このようにたくさんのデータ型を宣言することができます。これらの違いは格納できるデータの範囲が異なる事です。
以下の値は、一般的な場合における保持可能な値の範囲です。

char                    -128~127
unsigned char           0~255
 
short                   -32768~32767
unsigned short          0~65535
 
int                     約-21億~約21億
unsigned int            0~約42億
 
long int                約-21億~約21億
unsigned long int       0~約42億
 
float                   1.18*10の-38乗~1.18*10の-38乗(有効数値7桁)
 
double                  2.23*10の-308乗~1.79*10の-308乗(有効数値15桁)




取りえる範囲をプログラムで確認する


上記の値は厳密には、使用しているコンパイラによって変わってきます。
具体的な制限値を知りたい場合は、以下のプログラムで確認することができます。

#include <stdio.h>
#include <limits.h>
 
main( void )
{
    printf( "char              の範囲 %d~%d\n", CHAR_MIN, CHAR_MAX );
    printf( "unsigned char     の範囲 0~%u\n",  UCHAR_MAX );
    printf( "int               の範囲 %d~%d\n", INT_MIN, INT_MAX );
    printf( "unsigned int      の範囲 0~%u\n",  UINT_MAX );
    printf( "long int          の範囲 %ld~%ld\n", LONG_MIN, LONG_MAX );
    printf( "unsigned long int の範囲 0~%lu\n",  ULONG_MAX );
}



これを手元の環境(windows7,cygwin)で実行すると、以下の結果となりました。

$ gcc limit.c -o limit; ./limit
char              の範囲 -128~127
unsigned char     の範囲 0~255
int               の範囲 -2147483648~2147483647
unsigned int      の範囲 0~4294967295
long int          の範囲 -2147483648~2147483647
unsigned long int の範囲 0~4294967295

[C言語入門]計算した値を変数に保存する

前回は、指定された値を単に印字するだけのプログラムを作成しました。
コンピュータは日本語だと”電子計算機”と呼ばれるぐらいなので、今回は計算処理を行うプログラムを作成してみます。

今回のプログラムは以下の通りです。

#include <stdio.h>
 
main()
{
    int result;
 
    result = 10 + 20;
    printf( "result is %d\n", result );
}



これを実行すると下記の出力が行われます。
プログラム中にあった10+20の計算結果が表示されています。

$ gcc calc.c
 
$ ./a.exe
result is 30




それでは、今回のプログラムの詳細を確認していきます。
まずは最初の行ですが、これは計算結果を保存する場所を作っています。

    int result;


intというのはデータの型を意味していて、ここでは整数(integer)が保存できることを意味します。その後にあるresultは変数名です。
この宣言を行うことで、これ以降resultという名前で値を格納することができるようになります。

データ型についての詳細は、こちらの記事を参考にしてください。
[C言語入門]変数が保持できる値の範囲について


次の行では計算処理を行っています。

    result = 10 + 20;


数学の計算式と同じように”+”記号で足し算を行うことができます。


最後に、printfで出力を行います。

    printf( "result is %d\n", result );


前回は固定の文字を出力していましたが、今回はresultの値を出力することになります。
この場合、””で囲まれた文字列の中に%dを書くことで、画面に表示される値を変数に置き換えることができます。
何に置き換えるかは、文字列の後に”,”で区切り、変数名を書くことで指定することができます。

[C言語入門]printfの書式指定

前回のサンプルでは、printfの引数に%dを指定する事で、int型の変数値を表示させていました。
この%dの事を書式指定子(conversion specifier)と呼びます。


よく使用される書式指定子には以下のものがあります。
%の後に変換文字を書く事で表示方法を指定できます。

%d	整数を表示させる
%s	文字列を表示させる
%c	指定された文字コードに対応する,ASCII文字を1文字分表示させる
%f	浮動小数点の値を表示(doubleまたはfloat)
%d	浮動小数点の値を表示(double)
%x  16進数で値を表示
%g	数字を指数表現で表示
 
%p  ポインタ変数のアドレスを表示



さらに、%と変換文字の間には、以下の文字を書く事もできます。

桁数指定の数字
マイナス記号
精度



精度は、lを書く事でlongを意味し、hがshortを意味します。

%hd		short intの値を表示
%ld		long intの値を表示
%lld	long long intの値を表示



桁数指定の数字は、指定した分の幅を確保してくれます.

printf( "[%4d]", 12 );              /* [  12]      と表示される */
printf( "[%8.1f]", 12.345 );        /* [   12.345] と表示される */




マイナス記号を使うと、左寄せで表示させることが出来ます。

printf( "[%-4d]", 12 );             /* [12  ]      と表示される */
printf( "[%-8.1f]", 12.345 );       /* [12.345   ] と表示される */



印字幅を、固定ではなくプログラムから指定したい場合は”*”を使用します。

printf( "[%-*d]", 4, 12 );          /* [12  ]      と表示される */
printf( "[%-*.*f]", 8, 1, 12.345 ); /* [12.345   ] と表示される */



パーセント自体を出したい場合は”%%”と表記します。

printf( "%d%%", 12 );               /* 12%      と表示される */


[C言語入門]画面にメッセージを表示させてみる

文法とか細かいことは置いといて、プログラムを1つ作ってみます。
作業は、windowsにインストールしたcygwin環境で行います。


まず、下記のファイルをエディタで作成します。

#include <stdio.h>
main()
{
    printf( "hello world\n" );
}



作ったファイルは、以下のフォルダに保存してください。
(usernameの部分は、Windowsにログインしているユーザ名になります)
C:\cygwin\home\username\work


保存できたらlsコマンドで作ったファイルが存在することを確認し、コンパイルを行います。
コンパイルエラーが無ければ、a.exeというファイルが存在しているはずです。

$ ls
hello.c
 
$ gcc hello.c
 
$ ls
a.exe  hello.c



“./a.exe”コマンドで作ったプログラムを実行し、”hello world”という文字が出ればOKです。

$ ./a.exe
hello world



プログラムを見てみるといろいろな記述がありますが、順にチェックしてきます。
まず、1行目の#includeに関しては、とりあえず常に書く”おまじない”と思ってください。
(後述するprintf()関数を使用できるようにするための指令のようなものです)

2,3,5行目の “main()”, “{“, “}”も、とりあえずはお約束です。

4行目にはprintf…という行があり、ここでコンピュータに実行させる命令を指示しています。
プログラムの内容は、mainの”{“, “}”内に記述していきます。


再度4行目を見てみると、printfに対してカッコの中で、表示される文字を指定しています。
printfはコンピュータに対して何をさせるかを指示する命令で、これをC言語では”関数”と呼びます。

#include <stdio.h>
 
main()
{
    printf( "hello world\n" );
}



この中のメッセージを以下のように変更すれば、好きな文字を表示させることができます。

    printf( "hello japan\n" );


ダブルクォーテーション記号(“)で囲まれたメッセージを文字列と呼び、これはprintf関数に対して渡す付加情報となります。
一方、printf関数から見るとカッコ内に囲まれた情報のことを、引数(ひきすう)と呼びます。



また、複数のprintf関数を書くことで、複数行のメッセージを表示できます。
C言語では、複数の関数を書いた場合、上から順に実行されていきます。

#include <stdio.h>
 
main()
{
    printf( "hello world\n" );
    printf( "hello japan\n" );
    printf( "hello tokyo\n" );
}



上記プログラムの実行結果は、以下のようになります。

$ gcc hello.c
 
$ ./a.exe
hello world
hello japan
hello tokyo




printf関数の引数として渡した文字列のには、最後に”\n”という文字が入っていますが、この文字はC言語では改行文字を意味します。
“\n”の改行文字無しでprintfを実行すると、以下のようにメッセージが1行で表示されることになります。

#include <stdio.h>
 
main() 
{
    printf( "hello world" );
    printf( "hello japan" );
    printf( "hello tokyo" );
}



$ gcc hello.c
 
$ ./a.exe
hello worldhello japanhello tokyo




また、このルールを使用することで、最初に作成した”hello world”を表示するプログラムを、あえて複数の命令に分けて記述することもできます。
(ただし、人が読みづらくなるのであまりこのような書き方はしませんが…)

#include <stdio.h>
 
main() {
    printf( "hell" );
    printf( "o wo" );
    printf( "rld\n" );
}





今回のまとめ


今回学んだことは以下の3点です。

プログラムはmain()の中に記述していく。
プログラムは、上から書いた順に実行されていく。
画面に文字を表示させるためには、printf関数を使用する。
関数には、動作内容を規定するために、引数を渡すことができる。



[cygwin]gccで作ったプログラムで,日本語が文字化けする

windowsのcygwin環境にてgccでプログラムを作成した場合、日本語が文字化けする場合があります。
これは、C言語のソースファイル(.cファイル)と、使用しているターミナルの文字コードが一致していないのがよくある原因です(ターミナルというのは、Cygwin Terminal/xterm/コマンドプロンプトetcの事で、いわゆるコマンドを打ち込む”黒い画面”の事です)。


対処法は、プログラムを保存する際に.cファイルの文字コードをutf-8で保存すれば、正しく出力できます。

現在保存されいるソースファイルの文字コードは、fileコマンドで確認できます。

$ file test01.c
test01.c: UTF-8 Unicode C program text



ファイルの文字コードを変更する方法は色々ありますが、Windows Vista/Windows 7以降のOSを使用している場合は、Windowsに標準で入っているメモ帳から変更するのが簡単です。
ファイル->名前をつけて保存で、表示されるダイアログの右下にある”文字コード”を、UTF-8にしてから保存ボタンをクリックすれば変更されます。




また、文字コードを変更できない/変更したくない場合で、かつ、コンソールにCygwin Terminalを使用している場合は、以下の方法で出力される文字コードをSJISに指定することで、文字化けを回避できます。

ウィンドウ右上のアイコンを右クリック


Options…をクリックし、左メニューからTextを選択
画面右下のLocale,Character setを ja_JP, SJISにする



但し、上記の方法で出力文字コードを変えると、SJISで保存したプログラムを文字化けせずに出力させることはできますが、今度は他のコマンドの出力が文字化けしてしまいます。なので、あまりお勧めではないです。

[パタヘネ:読書メモ]第5章 容量と速度の両立:記憶階層の利用 その3

5.4 仮想記憶



仮想記憶を使う目的は主に2つある

複数プログラム間でメモリを効率よく使用する
主記憶以上のメモリ空間を(仮想的に)プログラムが使用できるようにする



後者は、仮想アドレスと物理アドレスの変換機能と、他プロセスから自プロセスのメモリ空間をアクセスされないための保護機能がある。

基本的に仮想記憶とキャッシュは同じ考え方だけど、歴史的な経緯により用語がそれぞれで異なっている。
キャッシュミスのことは、仮想記憶の世界ではページフォールトと呼ばれる。

ページフォールが起きるとペナルティが非常に大きい(メモリに対してディスクは10万倍くらい遅いから)なので、以下にページフォルトを抑えるかは非常に重要。

仮想記憶上のアドレスと実アドレスを対応付けるための仕組みをアドレスマッピング(またはアドレス変換)と呼ぶ。
また、仮想記憶があると、プログラムのローダは、他のプロセスの事を気にする必要がなくなるため、プログラムを任意の場所にロードできるというメリットがある。

キャッシュの場合、書き込み時の戦略にはライトスルーとライトバックの両方が選択してあったが、仮想記憶の場合は速度差が大きすぎるのでライトスルーは現実的ではない。
なのでライトバック方式が取られる。また、この方式だと連続領域に書き込みが行われる事になるので、ディスクへの書き込み時間が短くなる(ディスクをシークする頻度が相対的に下がるため)。
また、ページに書き込みが行われたかをdirty bitで管理し、変更されていないデータが追い出されたときは、ディスクへの書き込みを行わないようにする。



仮想記憶では、ページ表というものを使って、物理アドレスと仮想アドレスのメモリブロック間の対応付けを管理している。また、この対応表は最近参照されたものを再度参照する可能性が高い(空間的局所性)ので、キャッシュされる。これをTLB(translation-loolaside buffer:アドレス変換バッファ)と呼ぶ。

なので特定のデータが欲しい場合は、まずTLBを検索し、その後ページ表をチェックする事になる。
両者にデータが無ければ、CPUはOSに対してページフォールトの例外を投げる事になる。

仮想記憶の機能として、メモリのアクセス保護がある事を前述したけど、これをOSがサポートするにはプロセッサが以下の3つの機能を持っている必要がある。

実行中のプログラムのレベル(OS,ユーザプログラム)をプロセッサに伝えることが出来る事
    前者のレベルを,カーネルプロセス,スーパーバイザプロセス,エグゼクティブプロセス等と呼ぶ
 
ユーザプロセスが、CPUの情報を読み込みの可能なように行える事
    ここで言うCPUの情報とは、プログラムのレベル、ページ表ポインタ、TLB等を指している。
    上記の情報をWriteするには、スーパーバイザの権限があるときのみに出来る。
 
 
両者2つのレベルを移動できるようにする。
    ユーザモードからスーパーバイザモードへは通常システムコールによって行われ、
    MIPSではsyscall命令がそれに相当する。


上記3つが全てそろって初めて、ユーザプロセスは他プロセスからのメモリアクセスを保護することが出来る。


仮想記憶で問題になるのは、メモリの空きが少ないときにディスク<->メモリ間のデータ移動が頻繁になることである。
これをスラッシング(thrashing)と呼び、パフォーマンスが非常に低下するが、これを低減させる仕組みの1つとしてワーキングセットが有る。
ワーキングセットというのは、有るプロセスが同時に使用するページ郡を纏めたもので、メモリが少ない場合はワーキングセット内のメモリが破棄され内容にコントロールすると、スラッシングの発生を減少させる事ができる。ワーキングセットは大きすぎると、メモリ内に共存できるプロセス数が減るので同時実行性が損なわれる。一方、小さすぎるとこまめなキャッシュアウトが頻発するためページフォールトの頻度が増すので、程よいサイズを保つ事が重要。