Paepoi

スポンサードリンク
Paepoi » C and GLib Tips » gcc で C/C++

gcc で C/C++

# 最終更新日 2025.06.01

Linux 環境で gcc を使って C 及び C++ 言語のビルド方法と注意点。
Fedora 42 の gcc 15.1 にて動作確認しています、バージョンに注意しましょう。
macOS の gcc とは別物なので macOS の場合は Clang のほうをご覧ください。
C/C++ 言語そのものは解説しません、本でも買ってください。

このページではコマンドにてコンパイルする方法を解説します。
「ビルダー」等のツールはコマンドでビルドが普通にできる人が使うものです。

C 言語のビルド
1
2
3
4
5
6
7
8
9
10
/* hello.c */
#include <stdio.h>
 
#define hello "はろーわーるど"
  
int
main (int argc, char *argv[]) {
    printf("%s\n", hello);
    return 0;
}
日本語を出力する何の変哲もない C 言語コードを書き hello.c という名で保存。
リテラルに日本語を使う場合は LANG 変数と同じ文字コードで保存されている必要がある。
LANG は下記コマンドで確認、現行 OS は Windows 以外全部 UTF-8 ですけど一応。
1
2
3
4
5
echo $LANG
#=>ja_JP.UTF-8
#
# ja_JP.utf8 の場合もある、内部表記のことなので気にしない
#
エディタの保存文字コードが気になるなら cat や less コマンドで確認。
LANG と一致しているなら文字化けせずに表示できます。
コンパイルするには端末を起動してそのディレクトリに cd コマンドで移動します。
1
gcc hello.c
と打ち込むと a.out というファイルが作られます。
out という拡張子に意味は無い、単なるデフォルト名。
既に a.out というファイルが存在する場合は注意、容赦なく上書きされます。

コンパイルが成功すると何も表示されず入力待ちに戻ります。
Success とか ok なんて親切な出力はありません、UNIX ではそれが普通です。

実行パーミッション +x も付いているのでそのまま a.out を実行できる。
ただし当然パスは通っていないので「./」を付加、ドットは現在のパスに変換される。

gcc01

コードに間違いがあるとコンパイルエラーになり行番号付きで stderr が出力されます。
ただし以下のような間違いでは普通にビルドが通ってしまいます。
1
printf("%d\n", "文字列なのに %d 指定とか");
C 言語はそういうものだと思ってコーディングしましょう。


コマンドラインオプション
この方法だけでは全部 a.out という名前になる、-o オプションで別の名前を付けられます。
拡張子は不要というか無意味です、実行パーミッションがあれば動きます。
たとえば olympus.pen という名前にしたければ
1
gcc -o olympus.pen hello.c
他のオプションについては以下を参照。
gcc(1) - Linux manual page

分割コンパイル
例として関数を定義したファイル。
1
2
3
4
/* func.c */
int plus (int x) {
    return x + x;
}
と main 関数を定義したファイルを用意したとします。
1
2
3
4
5
6
7
8
9
10
/* main.c */
#include <stdio.h>
 
extern int plus (int x);
 
int
main (int argc, char *argv[]) {
    printf ( "%d\n", plus(3) );
    return 0;
}
後は gcc func.c main.c のようにソースコードを全部引数に指定する。
ディレクトリにまとめているなら gcc *.c のようにワイルドカードを使う方法もある。

注意、以前は extern 宣言無しでも Warning のみでビルド可能でしたが現在はエラーに。
とにかくオブジェクト同士が合体して func.c で定義した関数が利用できるようになる。

この方法はあまり良い手段ではないのでヘッダを作ってプロトタイプ宣言を書きましょう。
Clang のページ のようにするのが正しい方法です。

C++ 言語のビルド
C++ 言語のコンパイルは g++ コマンドを使います。
class, STL 等は C++ 標準なので当然 Linux からも利用できます。
iostream.h と書いている古い本もありますが C++ ヘッダに拡張子はありません。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// _stl.cc
#include <iostream>
#include <vector>
#include <algorithm>
  
/**
 * みんな大好き C++ のテンプレート
 * C++11 より unary_function は非推奨
 * なんだけど、継承する必要が無くなっただけだったり
 */
template <class T>
struct outputfunc { // : public std::unary_function<T, void> {
    void operator()(T obj) {
        std::cout << obj << "\e[31m" << obj << "\e[39m";
    }
};
  
class App {
public:
    App() {
        std::vector<std::string> vec = {"ある日", "森の中", "熊さんに", "出会った"};
        // 関数オブジェクトにしたけど今は範囲ベース for のほうがいい
        std::for_each(vec.begin(), vec.end(), outputfunc<std::string>());
        // 非同期ではありませんので
        std::cout << std::endl;
    }
    void gaoo(std::string_view text) {
        //文字列リテラルを std::basic_string として扱える
        text.remove_prefix(6);
        std::cout << text << std::endl;
    }
    ~App() {
        std::cout << "食べないで下さいー" << std::endl;
    }
    static auto init() {
        // auto で this を戻せる
        return new App();
    }
};
  
int
main(int argc, char* argv[]) {
    // イヂワルな初期化
    auto app = App::init();
    app->gaoo("voice@がおー食べちゃうぞ!");
    delete app;
    return 0;
}
Fedora 33 では上記で何のオプションも不要でビルド可能です。

gcc02

C++ ソースファイルの拡張子で一言、GNU 界隈では cc という拡張子が一般的です。
他に大文字の C や cxx でもいい、gcc の man ページ最初のほうに書いています。
Windows や macOS で使われる cpp もこの一覧に含まれるようになりました。

ただ g++ は内容が C++ であれば c の拡張子でも問題なくビルドできる。
とはいえ txt xls 等なんてありえない拡張子を試したら普通にエラーとなります。
UNIX の世界では拡張子は単なる目印で特別な意味はありませんが限度はあります。

UNICODE
Windows しか知らない人は UNICODE を扱う wchar_t は UTF-16LE で 2byte だと思っている。
gcc で wchar_t は UTF-32(UCS-4) で 4byte である、_t のサフィックスがある変数は環境依存。
UTF-32 と UCS-4 は定義した団体が違うだけで同じものと思ってください。
正確には広義が UTF-32 ですが GLib の関数が UCS-4 の名前を使っているので Linux 必須の知識になる。
リテラルの場合は文字数分になりますが最低が 4 バイトのようです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <wchar.h> // wchar_t
#include <uchar.h> // char32_t
 
// C11 対応の gcc にて
 
int
main (int argc, char const* argv[]) {
    /**
     * char16_t char32_t の確認
     */
    printf("リテラルのサイズは %lu バイト\n", sizeof('a'));
    printf("char のサイズは %lu バイト\n", sizeof(char));
    printf("wchar_t のサイズは %lu バイト\n", sizeof(wchar_t));
    printf("UTF-16(UCS-2) のサイズは %lu バイト\n", sizeof(char16_t));
    printf("UTF-32(UCS-4) のサイズは %lu バイト\n", sizeof(char32_t));
    return 0;
}
 
/* output
リテラルのサイズは 4 バイト
char のサイズは 1 バイト
wchar_t のサイズは 4 バイト
UTF-16(UCS-2) のサイズは 2 バイト
UTF-32(UCS-4) のサイズは 4 バイト
*/

x86_64
64bit アプリケーションではポインタサイズが 64bit(8byte) になる。
wchar_t のサイズが Windows とは違う他に 64bit では long も変わる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<stdio.h>
#include<stdlib.h>
 
int
main (int argc, char *argv[]) {
    int * p;
    printf ( "int     = %lu\n", sizeof(int) );
    printf ( "long    = %lu\n", sizeof(long) );
    printf ( "pointer = %lu\n", sizeof(p) );
    printf ( "char    = %lu\n", sizeof(char) );
    printf ( "wchar_t = %lu\n", sizeof(wchar_t) );
    printf ( "size_t  = %lu\n", sizeof(size_t) );
    return 0;
     
    /* output
    Linux x86_64 | Windows x64
                 |
    int     = 4  | int     = 4
    long    = 8  | long    = 4
    pointer = 8  | pointer = 8
    char    = 1  | char    = 1
    wchar_t = 4  | wchar_t = 2
    size_t  = 8  | size_t  = 8
    */
}
なので Linux なら long 変数にポインタを突っ込...
バグの元なのでヤメて注意しましょう。

C99|C11|C23
gcc は v10 から標準で C99 に対応、v15 は C11 にも対応しています。
c89|c99 というラッパースクリプトもある、c11 は無いですが標準対応なので。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<stdio.h>
 
// この形式のコメントも C99 から
// 以下のコマンドにすると C89 準拠になりビルドできない
// gcc -std=c89 hoge.c
// c89 hoge.c
 
int
main (int argc, char *argv[]) {
    /**
     * 変数宣言の簡易化
     */
    //int i;
    //for (i=0; i<3; i++) {
    for (int i=0; i<3; i++) {
        printf("%d\n", i);
    }
    /**
     * C11: fopen のモードに x 追加
     * 存在するファイルを開けないように指定
     * ちなみに fopen_s は Visual Studio だけ
     */
    FILE *fp = fopen("フジフイルムX100.txt", "w");
    if (fp == NULL) {
        printf("書き込みできません\n");
        return -1;
    } else {
        fputs("盗撮 CM で話題になったよね", fp);
        fclose(fp);
    }
    fp = fopen("フジフイルムX100.txt", "wx");
    if (fp == NULL) {
        printf("存在するので書き換えできません\n");
        return -1;
    } else {
        fputs("バカ売れしているけど", fp);
        fclose(fp);
    }
    return 0;
}
gets は削除されました、fgets 関数を使いましょう。
ちなみに gets_s は Visual Studio だけしか採用していません。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
  
int
main (int argc, char *argv[]) {
    char buf[1024];
    printf( "何か入力してください > " );
    //gets(buf); // gets は削除されました
    //gets_s(buf, sizeof(buf)) // Visual Studio だけです
    fgets(buf, sizeof(buf), stdin);
    // 行末の \n を取り除く、strdup が標準に取り込まれた
    char *line = strndup(buf, strnlen(buf, sizeof(buf)) - 1);
    printf("入力されたのは「%s」です\n", line );
    free(line);
    return 0;
}
C23 は一部対応、-std=c23 というオプションで使える。
auto が C でも使えるようになったけどイマイチ。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h> // free
#include <string.h> // strdup
 
int calc(int n) {
    return n * n;
}
 
int
main (int argc, char *argv[]) {
   
    // 静的文字列はイケる
    auto om = "OLYMPUS"  " " "Workspace";
    printf ("%s\n", om);
    // ポインタもイケる
    auto heap = strdup("おならプー");
    printf ("%s\n", heap);
    free(heap);
    // 計算値もイケる
    auto num = 3*5;
    printf ("%d\n", num);
    // 再代入も普通に可能
    num = 30;
    printf ("%d\n", num);
    // 関数の戻り値でも可能
    auto num2 = calc(8);
    printf ("%d\n", num2);
    // 配列とかは無理
    //auto num_array = {1, 2, 3};
    return 0;
}

C++11|C++14|C++17|C++20|C++23
gcc v15 は標準で C++17 まで標準対応しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <vector>
#include <algorithm>
 
auto mac() {
  return std::make_tuple("macOS", 14, "Sonoma");
}
 
int main (int argc, char const* argv[]) {
    /**
     * C++11 @ auto による型憶測や範囲ベース for ループ
     */
    auto om = {"OM SYSTEM", "LUMIX", "FUJIFILM X"};
    for (auto x : om) {
        std::cout << x << std::endl;
    }
    /**
     * C++14 @ ラムダ式の拡張
     */
    std::vector<std::string> v = {"LEICA", "Hasselblad", "FUJIFILM GFX"};
    std::for_each( v.begin(), v.end(), [] (auto s) -> void {
        std::cout << s << std::endl;
    });
    /*
     * C++ 17 @ std::pair や std::tuple を Python のように受け取れる
     */
    auto [os, ver, name] = mac();
    std::cout << os << ver << name << std::endl;
    return 0;
}
C++20 はオプションが必要。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <format>
 
/**
 * C++ 20 パラメータに auto でもエラーにならない
 * パラメータと戻り値は同じ型になるようです
 * 又 Python や C# でおなじみのフォーマットもできるように
 * g++ -std=c++20 cpp20.cc
 */
 
auto get_half(auto num) {
    return num / 2;
}
 
int main (int argc, char const* argv[]) {
    // 簡易オーバーロード
    std::cout << get_half(3) << std::endl;
    std::cout << get_half(3.0) << std::endl;
    // format
    auto om = std::format("最高なカメラは {} です\n", "OM SYSTEM");
    std::cout << om;
 
    return 0;
}
C++23 にも対応中。
1
2
3
4
5
6
7
8
9
10
11
#include <print>
 
// g++ -std=c++23 cpp23.cc
 
int main (int argc, char const* argv[]) {
    /**
     * println なら改行付き、いらないなら print
     */
    std::println("最高な{0}は{1}です", "カメラレンズ", "パナライカ");
    return 0;
}

Trigraph, Digraph
Trigraph は昔コンピューターで表記できる文字が少なかった時代に規定された三文字表記。
日本語キーボードは恵まれていますがコレが無いと困る言語圏もあったのです。
トライグラフ - Wikipedia
削除されたと注記がありますが -std=c89 オプションならまだ使えます。
ちなみに c89 指定なら main 関数の引数や戻り値はオプション扱いです。
1
2
3
4
5
6
7
8
9
10
11
??=include <stdio.h>
 
/**
 * -trigraphs オプションにて利用可能ですが制限あり
 * -std=c89 オプションにて C89 準拠で書く必要があります
 * gcc -std=c89 -trigraphs hoge.c
 */
  
main() ??<
    printf("%s??/n", "はろー Trigraphs");
??>

Digraph は C99 で規定されたチョッピリ洗練版。
gcc はオプション無しで使えますが main 関数の戻り値指定が必須になりました。
1
2
3
4
5
6
%:include <stdio.h>
  
int main() <%
    printf("%s\n", "はろー Digraph");
    return 0;
%>

現在はキーボードにブレース等が無い言語圏でもすべて右 Alt キー経由で入力できるようです。
ということで歴史の長い C 以外の言語ではンなことできませんのであしからず。

Makefile
Fedora 42 では make コマンドは最初から入っているはずです。
もし存在しない環境であれば入れておきましょう。
1
2
# Fedora の場合
sudo dnf install make
make コマンドは実行すると同一ディレクトリにある Makefile というファイルを探します。
拡張子は無し、かつ大文字小文字はこのままのファイル名である必要があります。
そしてその Makefile を読み取り記述された各種コマンドを実行します。
以下の内容で拡張子を付けない Makefile という名前のファイルを作成。
注意としてインデントは \t で行うのを忘れないように。
1
2
3
# Makefile
hayabusa: suzuki.c
    gcc -o hayabusa suzuki.c
それを suzuki.c というソースがあるディレクトリに置きましょう。
そして端末から make コマンド、hayabusa という実行ファイルが作成されます。
Makefile の書き方については以下のサイトが解りやすかった。
Makefileの解説

GLib
GNOME 関連アプリのソースコードを見ると printf や char なんて使われていません。
GLib によるマクロ g_printf や gchar で記述されている。
様々な理由がありますが GLib に少しでも係るアプリを作るならこちらを使いましょう。

GLib ヘッダを include するには pkg-config 指定が必要です。
1
`pkg-config --cflags --libs glib-2.0`
GLib には日本語を扱うのに便利な関数も用意されている。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <glib.h>
#include <glib/gprintf.h>
  
/* gcc test.c `pkg-config --cflags --libs glib-2.0` */
  
int
main (int argc, char *argv[]) {
 
    gchar *c;
    gchar *n;
    GError *error;
    gunichar *ucs;
    glong len;
    gint i = 0;
 
    c = g_strdup("バーグマン 400");
    /* 漢字も一文字として長さを得る */
    g_printf ("%s は %ld 文字です\n", c, g_utf8_strlen(c, 1024));
    /* 漢字も一文字として切り取り */
    n = g_utf8_substring(c, 0, 5);
    g_printf ("%s 200\n", n);
    g_free(n);
    /* アルファベットのみ大文字化 */
    n = g_utf8_strup("Suzuki Hayabusa カッコイイ", -1);
    g_printf("%s\n", n);
    g_free(n);
 
    /* UTF-32(UCS-4) に変換 */
    ucs = g_utf8_to_ucs4 (c, -1, NULL, &len, &error);
    g_free(c);
    /* エラーチェック */
    if (!ucs) {
        g_printf (error->message);
        gint retval = error->code;
        g_error_free (error);
        return retval;
    }
    /* 一文字づつ書き出し */
    for (i; i<len; i++) {
        n = g_malloc0(4);
        g_unichar_to_utf8 (ucs[i], n);
        g_printf ("%s\n", n);
        g_free(n);
    }
    g_free(ucs);
    return 0;
}
これ以上細かいことは公式ドキュメントを参照ください(英語)。
GLib – 2.0

GTK+
GTK+ ヘッダを include するにはやはり pkg-config 指定が必要です。
コンパイル毎に打ち込みなんてアホクサイので Makefile を利用しましょう。
1
2
3
# Makefile
gtkapp: test_gtk.c
    gcc -o gtkapp test_gtk.c `pkg-config --cflags --libs gtk+-4.0`
※gtk+ により glib, gio はインクルードされるので別途指定は不要

そして肝心なコード、ここでは GtkWindow を表示するだけの最小限サンプル。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <gtk/gtk.h>
 
static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  GtkWidget *window;
 
  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");
  gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
  gtk_window_present (GTK_WINDOW (window));
}
 
int
main(int argc, char *argv[]) {
  GtkApplication *app;
  int status;
 
  app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
 
  return status;
}
Gtk – 4.0: Getting Started with GTK
をコピペしただけです、もっとヤリたい人は勝手に勉強してください。

GTK4 では GTK3 までと違い Widget が標準で表示になりました。
gtk_main 関数が廃止になったので GtkApplication が必須になりました。
GtkApplication がメインループを担当、管理する Window が無くなった時点で終了します。
他に Widget パッキング情報は子側が持つ等 GTK3 とはゴロッと変わっています。

C 言語なので PyGObject のようなメソッドではなく関数をひたすら利用していきます。
GTK3 までと同様に関数の最初でターゲットを指定してパラメータを書いていく流れです。
それと GTK+ 初期化関数の呼び出しは必須なので忘れないように。
これ以上細かいことはリファレンスマニュアルを参照ください。
Gtk – 4.0
Copyright(C) sasakima-nao All rights reserved 2002 --- 2025.