7839

雑草魂エンジニアブログ

【C/C++】MacでC/C++実行環境構築(Clang→GCCの環境構築)

C言語は大学の授業でやったぐらいで10年ぶりの学び直し。今回は、一週間で身につくC言語の基本を教材として使わせていただいた。これが無料公開されているなんて、本当に感謝しかない。

早速、実行しようとした際に、少し戸惑ったので、調べたこともあわせて、備忘録として残しておく。

Visual Studioをインストール...不要

教材の手順に従って、MacVisual Studio 2019 for Macをインストールした。 早速プロジェクトを作成しようとしたが、C++がサポートされていない。。。そして、HPを確認すると、以下のように明記されていた。

Visual Studio for Mac では、Microsoft C++ はサポートされていませんが、.NET 言語とクロスプラットフォームの開発はサポートされています。

そこで、Macでは、Visual Studio Code をこれまで通り使うことにした。

C/C++環境構築

C/C++コンパイルするためには、GCCGNU Compiler Collection)が必要である。 そこで、現在の私のMacの環境を確認した。

$ where gcc
/usr/bin/gcc
$ gcc -v
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 13.0.0 (clang-1300.0.29.3)
Target: x86_64-apple-darwin20.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

本来のgccではなく、代替コンパイラであるclangがすでにインストールされていた。(Xcodeをインストールする際に、自動的にデフォルトのコンパイラとしてClangがインストールされているようであった。)

Clangとは

ClangはC、C++Objective-CObjective-C++をターゲットとして、マシン語コンパイルするために使用されるフロントエンド コンパイラで、LLVM上で動作することを意図して設計されている。

フロントエンド コンパイラと呼ばれている意味としては、コンパイラにはフロントエンドとバックエンドが存在し、Clangはそのフロントエンド、すなわちコマンドの入力、ソース解析を担当している。そして、バックエンドとして、対象のアーキテクチャに最適なマシン語へ変換しているのが、LLVMである。

f:id:serip39:20211205184348j:plain

なんとなくであるが、イメージを掴むことができた。LLVMでの最適化などの詳細は以下を参照してほしい。

GCCと比較して、ソース解析の際に、細かいWarningやErrorを出力してくれる。

GCCのインストール

今回のC言語の初歩的な勉強であれば、Clangでも全く問題ないが、あえてGCCの環境構築を行ってみた。

Homebrewを用いるので、事前にインストールしておいてほしい。

$ brew install gcc
$ ls /usr/local/bin | grep gcc- 
gcc-11
gcc-ar-11
gcc-nm-11
gcc-ranlib-11
$ ls /usr/local/bin | grep g++- 
g++-11

これで、GCCが使えるようになった。ただし、gcc-11g++-11のように末尾にバージョン情報が記載されているので実行時には注意が必要である。以下のコマンドで実行ファイルを生成することができる。

$ /usr/local/bin/gcc-11 test.c

$ /usr/local/bin/g++-11 test.cpp

開発環境構築

実行時には、以下のコマンドを実行する。

/usr/local/bin/gcc-11 test.c && ./a.out

毎回、同じコマンドを入力したくなかったので、シェルスクリプトを作成した。ただし、実行ファイルだけは、引数で指定するようにした。

#!/bin/bash
/usr/local/bin/gcc-11 $1 && ./a.out

実際に、実行してみる。

$ ./run.sh src/test.c
zsh: permission denied: ./run.sh

権限を確認する。

$ ls -l
total 8
drwxr-xr-x  3 serip39  staff   96 11 24 01:09 docs
-rw-r--r--  1 serip39 staff   29 11 24 01:22 run.sh
drwxr-xr-x  5 serip39  staff  160 11 24 01:09 sample

すべてのユーザーに実行権限を付与する。

$ chmod +x ./run.sh
$ ls -l
total 8
drwxr-xr-x  3 serip39  staff   96 11 24 01:09 docs
-rwxr-xr-x  1 serip39  staff   29 11 24 01:22 run.sh
drwxr-xr-x  5 serip39  staff  160 11 24 01:09 sample

これで、再度実行する。

$ ./run.sh src/test.c
HelloWorld.

無事に実行することができた。 これで、MacでのC/C++の実行環境ができた。

複数ファイルのコンパイル

ファイル分割を行い、自作のヘッダーファイルを読み込んで実行しようとすると、以下のようなエラーが出力された。(Clangのエラー文のほうがわかりやすかったので、Clangで実行した結果を示す。)

Undefined symbols for architecture x86_64:
  "_avg", referenced from:
      _main in main-584f2f.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

エラーメッセージをよく見ると、これはコンパイルのエラーではなく、リンカのエラーであった。gccやg++などのフロントエンドコンパイラコマンドは、コンパイルだけじゃなく、リンカまで行って実行可能ファイルを作成する。そのため、以下の方法で実行する必要がある。

  1. -cオプションなどでリンカを抑止する

    $ /usr/local/bin/gcc-11 -c src/test.c && ./a.out
    
  2. すべてのファイルを指定する
    リンカ時には、実行可能ファイルに必要な全てのファイルを指定する必要がある。(標準のライブラリは自動的に探索されるので指定不要。)

     $ /usr/local/bin/gcc-11 src/test.c src/test1.c src/test2.c && ./a.out
    

ゆえに、シェルスクリプトも以下のように変更を行った。

#!/bin/bash

if [ -f $1 ]; then
  /usr/local/bin/gcc-11 $1 && ./a.out
else
  for pathfile in `find $1/*c`;
  do
    allpath+=$pathfile
    allpath+=" "
  done
  /usr/local/bin/gcc-11 $allpath && ./a.out
fi

フォルダを引数として指定した場合は、フォルダ内のソースファイルをすべてgccの引数に指定できるようにした。

C言語コンパイル処理

C言語コンパイル処理に関して、整理しておく。

f:id:serip39:20211206003228j:plain

1. プリプロセッサ(Pre-processor

コンパイルする前の前処理を行う。コンパイラによるソースコードの字句解析や、構文解析、バイナリコード生成などよりも前に実行される。

  • ヘッダファイル(#include)の読み込み
  • マクロの展開(#define等)

この段階で処理を止める場合のコンパイラオプションは-Eである⁠。

2.(狭義の)コンパイラ

前処理済みのプログラムファイルをアセンブラ言語に変換する。アセンブリ言語は、機械が理解できるコードをシンボル(記号)で表現したものである。

この段階で処理を止める場合のコンパイラオプションは-Sである⁠。

3. アセンブラ(Assembler)

アセンブリ言語プログラムをアセンブルして、機械語のオブジェクトファイルに変換する。

この段階で処理を止める場合のコンパイラオプションは-c⁠である。

4. リンカ(Linker)

複数のオブジェクトファイル、標準ライブラリのオブジェクトファイルなども結合して、実行可能形式のファイルを生成する。

まとめ

C言語コンパイラ周りについて、調べてなんとなく概要を掴むことができた。この領域は本当に奥が深いので、これから少しずつではあるが、CPUなどの低レイヤーの部分まで理解できるようになると、もっと面白いのではないかと思えた。

とりあえずは、環境構築ができたので、Cの勉強に取り掛かっていきたい。