組み込みの埋まってるとこ

システム寄りの組み込みCプログラミングBLOG

printfで何も表示されない原因と対応方法

printfで何も出力されないときに考えられる原因とその対処方法を一般的なことから少し専門的な分野までまとめてみます。

Windows環境でprintfが出力されない

Windowsではprintf出力はいったんバッファに蓄えられ、バッファの容量を超えた時点で出力されます。 そのため出力する文字数が少ないときは直ちに出力されません。 例えばMinGWgccコンパイルした場合はprintfの出力が4096バイトを超えるまではバッファされ出力されません。

一方でMSYSのgccコンパイルした場合は改行コードが現れる度に出力されるようになります。 MinGWWindowsネイティブでコンパイルするのに対し、MSYSはPOSIX準拠であることがこうした違いとなって現れているのでしょう。

バッファにたまっていて出力されない

printfの出力は比較的遅い処理になるためある程度溜まってからまとめて出力することがあり、 これが原因でprintfがすぐに出力されないことがあります。

改行文字で出力させる

バッファのモードがラインモードになっているときは改行文字('\n')が現れるまで出力がバッファされその間出力されなくなります。 標準Cライブラリの仕様として標準出力はラインバッファモードになっていることが多いので、誰でも一度は経験するトラブルではないかと思います。

これが原因で出力されないときは、printfの出力に改行文字を追加するとあっさりと出力されるようになります。

printf("Hello world\n");  /* \nが実は重要 */

強制的にフラッシュする

バッファがブロックモードかラインモードになっていて、 出力タイミングがはっきりしないことが問題になっているときは 強制的に出力する方法で解決できます。

バッファに溜まっている出力を流しだすことをフラッシュと言いますが、 プログラムで強制的にフラッシュするにはfflush関数を使います。

fflush(stdout);

fflushとstdoutの宣言はstdio.hにあります。

バッファを無効にする

いちいちフラッシュするのが面倒なときはバッファモードを無効にすることがあります。 あまり使われない方法ですが2~3文字の少ない文字数を頻繁に出力するようなプログラムでは有効なこともあります。

setvbuf(stdout, NULL, _IONBF, 0);

setvbufの宣言はstdio.hにあります。

文字コードが違っていて出力が表示できない

プログラムに埋め込んでいる文字コードと出力先が対応している文字コードが同じでない場合は文字化けしたり最悪何も表示されないことがあります。 printfで出力しようとしている文字が0-9,A-z の範囲のアスキーコードだけであれば問題になることはほとんどありませんが、全角文字のようなマルチバイト文字出力しようとしているときは注意が必要です。稀なケースでフォントが無いこともあります。

文字コードの対応を調べる

まずprintfのダブルクォーテーション(")で囲まれた文字列がコンパイルした後 どんな文字コードになっているかを意識します。 基本的にはプログラムソースを保存するときに指定した文字コードになっているはずです。

例えばプログラムソースをUTF-8で保存している場合はprintfが出力する文字コードUTF-8になるため出力先がUTF-8を表示可能でなければ正しく表示できないことになります。

printf(u8"3バイトのUTF-8は私のターミナルで表示できますか?\n");

文字コードを指定する

プログラムソースを保存するときに指定できる文字コードコンパイラによって決まっていて自由に選ぶことはできません。デバッグしていてどうもおかしいと思ったらプログラムソースの文字コードが違っていたというこもともままあります。

ロスコンパイラを使ってる等の理由であえてプログラム実行時にprintfの出力する文字コード、 言い換えるとオブジェクトファイルの中に埋め込まれる文字コードを変更したい場合は コンパイラコンパイルオプションによって指定できることがあります。 gccでは-fexec-charsetで指定できます。

gcc -finput-charset=utf-8 -fexec-charset=cp932 modern.c -o legacy_ja

標準出力の出力先が無い

printfの出力先はCライブラリ的には標準出力(stdout)になっていますが、 実際に標準出力がどこになっているかはプログラムを実行する環境によって変化します。

実行環境で用意されたライブラリの関数を使う

特にウインドウを表示するプログラムではプログラム実行時に標準出力の出力先が変わったりなくなったりしていることがあります。 この場合printfに代わってデバッグ用に文字列を出力する関数がそのプログラムのライブラリによって用意されているはずなのでそちらを使うようにします。

WindowsではOutputDebugStringが有名です。 この関数は出力先がデバッガのウインドウになりますが、 言語ランタイムサポートやデバッガやの設定により出力先が変わったり拾えなかったりして表示できなくなるトラブルがたまにあるので注意です。 またMinGWで開発していて純正のデバッガがないときはDebugViewというツールで拾う方法もあるようです。 どうしてもコンソールに出力したいときは使い方が難しいですがWriteConsoleという関数もあります。

組み込みで標準出力ドライバをつくる

組み込みプログラムではターゲットボードに標準出力のドライバがないためにprintfが出力されないケースが山のようにあります。 最初からMyPrintfでUARTへ出力するようなライブラリを用意しているプロジェクトも普通によく見かけます。

しかし既にUARTへ出力することができているのであれば、 標準Cライブラリのローレベルをポーティングして printfを使えるようにすることは難しいことではないはずです。 興味があればライブラリのマニュアルを見るとポーティング方法の説明が書かれていると思います。 自力でドライバを作らないまでも、 JTAGデバッガを経由して統合開発環境のコンソールにprintfを出力する方法が説明されていることもしばしばあります。

int _write(int fileno, const char *buf, int size)
{
  if (fileno==1 || fileno==2) {
    puts_uart(buf, size);
    return size;
  }
  return 0;
}

まとめ

一般にprintfが出力されない原因は標準Cライブラリによる出力のバッファです。 この場合は改行コードを出力するか、明示的にfflushで流しだすかのどちらかで出力することができます。

printfの出力先は普通は標準出力という名前のコンソールですが、 それが環境によりそうではなくなってしまっていることもよくあります。 この場合システム的な知識がないと解決が難しくなりますが、 出力先がどこなのか検討がつくだけでも検索などで調べやすくなるのではないかと思います。

ドント方式で選挙の当選者を決定するプログラム

令和7年度大学入学共通テストからの出題教科・科目である情報のサンプル問題を題材に、 ドント方式で当選政党を決定する選挙の当選者決定プログラムをつくってみました。 C言語で書かれていますが基本的なプログラムなのでJavaPython等他の言語でも参考になるでしょう。

プログラムの解説

アルゴリズムの解説は大学入試センターにある試験問題の本文をご覧ください。

令和7年度以降の試験に向けた検討について|大学入試センター

プログラムソース

ドント方式で当選政党を決定する選挙のC言語での解答例です。

#include <stdio.h>

#define NUM_OF_PARTY 4  /* 政党数 */
#define NUM_OF_SEAT  6  /* 選挙で選ばれる定員数 */

int main()
{
    /* 政党名と投票数のデータ */
    const char *party[NUM_OF_PARTY] = {
        "A Party","B Party", "C Party", "D Party"
    };
    int vote[NUM_OF_PARTY] = {
        1200, 660, 1440, 180
    };


    /* 総投票数を計算する */
    int amount = 0;
    for (int m = 0; m < NUM_OF_PARTY; m++) {
        amount += vote[m];
    }
    printf("Amount of vote: %d\n", amount);

    /* 当選政党を決めるときの比較に使う値を初期化する */
    double weight[NUM_OF_PARTY];
    for (int m = 0; m < NUM_OF_PARTY; m++) {
        weight[m] = (double)vote[m];
    }

    /* 政党ごとの当選者数を初期化する */
    int elect[NUM_OF_PARTY];
    for (int m = 0; m < NUM_OF_PARTY; m++) {
        elect[m] = 0;
    }

    /* すべての議席が埋まるまで当選政党を決定する */
    for (int seat = 0; seat < NUM_OF_SEAT; seat++) {
        /* 投票数を現時点での当選者数で割った値が一番大きな政党を当選とする */
        int maxParty = 0;
        for (int m = 0; m < NUM_OF_PARTY; m++) {
            if (weight[m] > weight[maxParty]) {
                maxParty = m;
            }
        }
        /* 当選政党の当選者数と比較値を更新する */
        elect[maxParty] += 1;
        weight[maxParty] = (double)vote[maxParty]/(double)(elect[maxParty] + 1);

#ifdef DEBUG
        printf("Term %d:", seat+1);
        printf(" Hikaku=");
        for (int i = 0; i < NUM_OF_PARTY; i++) {
            printf("%9.0f,", weight[i]);
        }
        printf(" Tosen=");
        for (int i = 0; i < NUM_OF_PARTY; i++) {
            printf("%7d,", elect[i]);
        }
        printf("\n");
#endif
    }

    /* 結果発表 */
    for (int m = 0; m < NUM_OF_PARTY; m++) {
        printf("%s: %d seat\n", party[m], elect[m]);
    }

    return 0; /* main関数正常終了 */
}

プログラムの応用方法

政党と定員はdefineの値だけで変更できるようにプログラムが工夫されています。 ただしこれに伴う政党名と投票数のデータの修正は必要になります。

プログラムの課題

どの政党の当選者名簿にも十分な人数の立候補者がいて、当選政党に選ばれたとき議席配分ができないことは起こらない前提で省略されている処理があります。これはいつか改良すべきです。

最後の当選者を決めるとき、同じ投票数の政党が複数あった場合はプログラム的なデータの検索順で議席が割り当てられます。極端な例では定員が1名で政党が2つあってどちらの政党も同じ投票数であったとき、何も警告を出さずに最初に検索された政党に議席を割り当て何事もなかったかのように終了してしまいます。これもいつか改良すべきです。

プログラムの考察

総投票数はint型の変数amountで表しています。 int型の範囲は-2147483648~2147483647ですので有権者数が日本の参議院比例代表ぐらいの規模の選挙であれば十分に計算可能です。計算の途中でオーバーフローが起こらないこともレビューでは確認しています。

投票数を当選者数で除算しているところがありますが、ここはdouble型で計算しています。 int型で除算した場合は少数点以下の数字が切り捨てになるため正確に比較できないケースが起こり得ると考えられそれを回避するためです。 double型であれば仮数部のビット数がint型のビット数よりも大きいため、今回のようにint型の投票数を除算した値で比較する用途であれば理論的に計算後の量子誤差は無視することが可能です。float型は仮数部のビット数がint型のビット数よりも小さいため、結果の有効ビット数が元のint型のビット数を下回ることになってしまい期待どおり比較することができません。