Paepoi

Paepoi » C and GLib Tips » Clang で C/Objective-c

Clang で C/Objective-c

# 最終更新日 2025.06.01

macOS 環境で Clang を使って C/C++ 及び Objective-c 言語のビルド方法と注意点。
macOS 15 Sequoia の Clang 17 にて動作確認しています、バージョンに注意しましょう。
なお、macOS の gcc は clang のラッパーです、gcc -v と打てば解ります。
言語そのものは解説しません、本でも買ってください。

あえてコマンドでコンパイルする方法を解説します。
Xcode は Xcode の使い方ばかり勉強するはめになるので使わないほうがいい。

C 言語のビルド
/* testc.c */
#include <stdio.h>

#define hello "はろーわーるど"
 
int
main (int argc, char *argv[]) {
    printf("%s\n", hello);
    return 0;
}
日本語を出力する何の変哲もない C 言語コードを書き test.c という名で保存。
リテラルに日本語を使う場合は UTF-8 で保存されている必要がある。
コンパイルするには端末を起動してそのディレクトリに cd コマンドで移動します。
clang ファイル名
と打ち込むと gcc 同様に a.out というファイルが作られます。
macOS も UNIX なのでコマンドでは拡張子に意味は無い、単なるデフォルト名。
既に a.out というファイルが存在する場合は注意、容赦なく上書きされます。

コンパイルが成功すると何も表示されず入力待ちに戻ります。
これも gcc と同じ、UNIX ではそれが普通です。

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

clang01

文字コードが違う場合もコンパイルは通りますが文字化けします。
Windows からソースコードを持ってきた場合 iconv 等で変換。

コードに間違いがあるとコンパイルエラーになり行番号付きで stderr が出力されます。
gcc 同様に細かいミスまでは発見してくれませんので慎重に。

コマンドラインオプション
gcc 同様に -o オプションで別の名前を付けられます。
たとえば fujifilm.x という名前にしたければ
clang -o fujifilm.x test_c.c
オプションについては gcc とほぼ同じです、まとめているサイト見つけました。
GCC と Clang のオプション概要 — SOLID 3.5.0 ドキュメント
分割コンパイル
Clang でも gcc 同様 extern 宣言も使えますがこの手法はあまり勧めません。
実装がどこにあるのか不明瞭なので規模が大きくなるとワケワカになります。
ここでは一般的なヘッダを作りプロトタイプ宣言を行う手法を。
/* main.h */

/* インクルードガード */
#ifndef SUZUKI_HAYABUSA
#define SUZUKI_HAYABUSA

/* プロトタイプ宣言 */
int plus (int x);

#endif
/* func.c */

#include "main.h"

int plus (int x) {
    return x + x;
}
/* main.c */

#include <stdio.h>
#include "main.h"

int
main (int argc, char *argv[]) {
    printf ( "%d\n", plus(3) );
    return 0;
}
後は gcc func.c main.c のようにソースコードを全部指定する。
インクルードガード、プロトタイプ宣言、extern宣言、の解説はしません。
#pragma once が実は gcc|Clang 共に使えるんですけどね。

C++ 言語のビルド
C++ 言語のコンパイルは clang++ コマンドを使います。
class, STL 等は C++ 標準なので当然 macOS も普通に利用できます。
// stl.cc

/**
 * Clang 17 では iostream 等のヘッダが見つけられません、バグ?
 * 当面は下記のように -I オプションで、場所は筆者の場合です
 * clang++ -I /Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk/usr/include/c++/v1 stl.cc
 */

#include <iostream>
#include <vector>
#include <algorithm>

template <class T>
struct outputfunc {
    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;
}

筆者は面倒なので ~/.zshrc に以下を追記して clang2 エイリアスを作っています。
以下の C++ コードもそのコマンドでやっていますのでお間違いなく。
alias clang2="clang++ -I /Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk/usr/include/c++/v1"

clang02

macOS では C++ ソースの拡張子は cpp が一般的ですが Linux 同様な cc でもいい。
Linux の g++ は拡張子が小文字の c なファイルでもビルドしますが Clang は警告を出す。
ファイル名の大文字小文字を区別しない仕様なので大文字の C を使うのは控えよう。

UNICODE
Clang で wchar_t は gcc と同じ UTF-32(UCS-4) で 4byte である。
ただ macOS の Clang では uchar.h が提供されていないようです。
u|U のプリフィックス指定は対応しているので下記のようにすることは可能。
#include <stdio.h>
#include <wchar.h> // wchar_t
//#include <uchar.h> // 存在しない

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(u'a'));
    printf("UTF-32(UCS-4) のサイズは %lu バイト\n", sizeof(U'a'));
    return 0;
}

/* output
リテラルのサイズは 4 バイト
char のサイズは 1 バイト
wchar_t のサイズは 4 バイト
UTF-16(UCS-2) のサイズは 2 バイト
UTF-32(UCS-4) のサイズは 4 バイト
*/

64bit
macOS は High Sierra を最後に 32bit サポートを打ち切った完全 64bit です。
CPU が M1 になりましたが 64bit のままです、つまり Linux の x86_64 と同じ結果になる。
#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 | macOS M1
                 |
    int     = 4  | int     = 4
    long    = 8  | long    = 8
    pointer = 8  | pointer = 8
    char    = 1  | char    = 1
    wchar_t = 4  | wchar_t = 4
    size_t  = 8  | size_t  = 8
    */
}
Intel Mac でも結果は同じ。

C99|C11|C23
Clang 17 は標準で C11 まで対応しています(// コメント等)
c89|c99 というラッパースクリプトは Clang でも使える。
#include<stdio.h>

// この形式のコメントも C99 から
// 以下のコマンドにすると C89 準拠になりビルドできない
// clang -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 は Clang でも削除されました、fgets 関数を使いましょう。
もちろん gets_s では gcc 同様に Clang でもエラーになります。
#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 でも使えるようになったけどイマイチ。
#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
Clang 17 ではオプション無しで使える標準は C++14 です。
C++17 までを有効にするには -std=c++17 オプションが必要。
#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 はオプションが必要。
#include <iostream>
#include <format>

/**
 * C++ 20 パラメータに auto でもエラーにならない
 * パラメータと戻り値は同じ型になるようです
 * 又 Python や C# でおなじみのフォーマットもできるように
 * clang2 -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 にも対応中。
#include <print>

// clang2 -std=c++23 cpp23.cc

int main (int argc, char const* argv[]) {
    /**
     * println なら改行付き、いらないなら print
     */
    std::println("最高な{0}は{1}です", "カメラレンズ", "パナライカ");
    return 0;
}

Trigraph, Digraph
Trigraph は Clang では削除されました、Digraph はオプション無しで使える。
右 Alt キー経由でないとブレースが打てない言語圏への配慮だと思いますけど。
日本語キーボードは恵まれているので使う機会なんて無いですが。
%:include <stdio.h>
 
main() <%
    printf("%s\n", "はろー Digraph");
%>

Makefile
make コマンドは Command Line Tools に含まれています。
make コマンドは Makefile というファイルを読み取り各種コマンドを実行します。
以下の内容で拡張子を付けない Makefile という名前のファイルを作成。
注意としてインデントは \t で行うのを忘れないように。
# Makefile
katana: suzuki.c
    clang -o katana suzuki.c
それを suzuki.c というソースがあるディレクトリに置きましょう。
そして端末から make コマンド、katana という実行ファイルが作成されます。

macOS はファイル名の大小文字を区別しないので makeFile とかの名前でも実はイケる。
他の UNIX では普通に区別するので標準に従いましょう。
Makefileの解説

Objective-c 言語のビルド
Objective-c って実はメチャクチャ簡単です。
C 言語ソースの拡張子を m に変更し clang に渡すだけです。
本当にそれだけでビルドできます、include を import に書き換える必要すら無い。

そりゃなんたって C 言語にメッセージ式等の追加をしただけの言語ですから。
Objective-c がワカランという人は C 言語もできないという自己紹介しているだけ。
脱線失礼、ただそれでは Objective-c を使う意味は無いので。
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>

// clang hello.m -framework Foundation

@interface Zou : NSObject
- (void) sayHello:(NSString *)str
           length:(NSUInteger)len;
@end

@implementation Zou
- (void) sayHello:(NSString *)str
           length:(NSUInteger)len {
    NSLog(@"ゾウの%@の長さは %ldm です。", str, len);
}
@end

int main(int argc, char *argv[]) {
    NSString *abc = @"うんち";
    NSUInteger l = [abc length];
    id zou = [Zou alloc];
    [zou sayHello:abc length:l];
    return 0;
}
-framework オプションでフレームワークの指定が必須です。
interface は通常ヘッダに書きます、後は C 言語が解るならすぐ理解できます。
ってコレだけ見ると Objective-c って面倒なだけに見えますよね。
この言語の醍醐味は Cocoa を使う時に解ります。

NSApp を作る
PyObjC で作った NSApp 同様なものをコードだけで作ってみます。
NSApplication は他の Application クラス実装と違いウインドウの無いアプリが作れます。
説明が逆のような気がするけど、気にしない。

ヘッダ、説明用に単純化したけど実用時は C 言語同様にパーツ毎に分離してね。
#import を使う場合はインクルードガードは不要だそうです。
/* app.h */

#import <Cocoa/Cocoa.h>

@interface AppMenu : NSMenu
@end

@interface AppDelegate : NSObject<NSApplicationDelegate>
@end
実装部、拡張子は m にする。
clang に渡すソースは拡張子 m にした実装部だけでいい。
/* app.m */

#import "app.h"

// clang app.m -framework Cocoa

@implementation AppDelegate
- (id) init {
    [super init];
    return self;
}
- (void) applicationDidFinishLaunching:(NSNotification *)aNotification{
    // アクティブ化
    [NSApp activateIgnoringOtherApps:YES];
}
@end

@implementation AppMenu
- (id) init {
    [super init];
    id item_app = [[NSMenuItem new] autorelease];
    [self addItem:item_app];
    id menu_app = [[NSMenu new] autorelease];
    [item_app setSubmenu:menu_app];
    id item_quit = [[NSMenuItem new] autorelease];
    [item_quit initWithTitle:@"Quit App" action:@selector(terminate:) keyEquivalent:@"q"];
    [menu_app addItem:item_quit];
    return self;
}
@end

int main(int argc, char *argv[]) {
    // ガベージコレクションではないのでコレを利用
    [NSAutoreleasePool new];
    // NSApp を作る
    [NSApplication sharedApplication];
    // C ではコレが必須だった
    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
    // command+Q で終了するメニューを入れる
    id main_menu = [[AppMenu new] autorelease];
    [NSApp setMainMenu:main_menu];
    // Delegate
    id delegate = [[AppDelegate new] autorelease];
    [NSApp setDelegate:delegate];
    // メインループを回す
    [NSApp run];
    //
    return 0;
}
注意点はアクティブ化するのはデリゲート内で行うこと。
main 関数内で行うと処理を抜けきれずメニューが使えなくなる。

こんな感じで Foundation のみの時と違いスッキリしたコードになる。
Windows SDK や GTK+ を C 言語で書いた経験がある人なら衝撃レベルです。
Objective-c は C 言語ベースですが C 言語を使う必要はほぼありません。
Cocoa だけでもイケるけど C の資産も使えるおいしい言語なんです。

Copyright(C) sasakima-nao All rights reserved 2002 --- 2025.