Untuk membuat eksekutabel Mac native dari beberapa file C++, berikan semua file implementasi yang diperlukan ke clang++, atau kompilasi file-file tersebut menjadi file objek lalu tautkan semuanya. Panduan ini membuat program Terminal kecil dan menjelaskan cara mendiagnosis definisi yang hilang.

Siapkan alat kompilator Mac

Gunakan alur kerja ini untuk program C++ kecil yang ingin Anda jalankan secara lokal di Terminal. Apple menyediakan kompilator dan SDK macOS melalui Xcode atau paket Command Line Tools mandiri. Jika Xcode sudah terpasang, Anda tidak memerlukan paket terpisah. Jika belum, jalankan perintah berikut dan selesaikan dialog instalasi. Lihat Menginstal alat baris perintah dari Apple.

xcode-select --install

Setelah instalasi, periksa apakah kompilator tersedia:

clang++ --version

Gunakan clang++ untuk penautan akhir, termasuk jika masukan Anda sudah berupa file objek hasil kompilasi. Dokumentasi Clang secara khusus merekomendasikan driver ini untuk program C++ karena mode bahasa menentukan pustaka runtime mana yang ditautkannya secara otomatis. Lihat Menyusun toolchain lengkap.

Bangun program C++ lengkap dengan tiga file

Contoh buatan ini menghitung barang pada tiga baki meja dan memformat hasilnya. Program ini memiliki satu titik masuk dan dua file implementasi pendukung. Buat direktori kerja baru yang kosong, lalu simpan keempat file ini langsung di dalamnya:

desk-demo/
  desk.hpp
  main.cpp
  label.cpp
  totals.cpp

Simpan deklarasi bersama sebagai desk.hpp:

#ifndef DESK_DEMO_DESK_HPP
#define DESK_DEMO_DESK_HPP

#include <string>

std::string make_label(int count);
int total_items(int trays, int per_tray);

#endif

Simpan titik masuk sebagai main.cpp:

#include "desk.hpp"
#include <iostream>

int main() {
    const int count = total_items(3, 4);
    std::cout << make_label(count) << '\n';
    return 0;
}

Simpan implementasi pemformatan sebagai label.cpp:

#include "desk.hpp"

std::string make_label(int count) {
    return "Desk items: " + std::to_string(count);
}

Simpan implementasi penghitungan sebagai totals.cpp:

#include "desk.hpp"

int total_items(int trays, int per_tray) {
    return trays * per_tray;
}

Setiap file implementasi menyertakan header yang sama, sehingga kompilator dapat memeriksa definisi fungsinya terhadap deklarasi bersama. Penjaga header mencegah penyertaan berulang dalam satu unit translasi. Letakkan badan fungsi biasa yang non-inline ini di file implementasinya; C++ menetapkan persyaratan definisi di seluruh program melalui aturan satu definisi.

Kompilasi dan tautkan dengan satu perintah

Buka Terminal di direktori yang berisi keempat file, lalu jalankan:

clang++ -std=c++17 main.cpp label.cpp totals.cpp -o desk-demo && ./desk-demo

Keluaran yang diharapkan:

Desk items: 12

Perintah ini mencantumkan ketiga file implementasi dan menetapkan nama file keluaran dengan -o. Karena tidak ada opsi untuk menghentikan proses pada tahap tertentu, Clang mengompilasi masukan dan menjalankan linker. -std=c++17 yang eksplisit memilih versi bahasa alih-alih mengandalkan nilai bawaan kompilator. Opsi ini didokumentasikan dalam Panduan perintah kompilator Clang.

Shell menjalankan eksekutabel hanya jika proses build berhasil karena perintah-perintah tersebut digabungkan dengan &&. Ini penting saat membangun ulang: build yang gagal seharusnya tidak diikuti dengan menjalankan eksekutabel lama secara tidak sengaja dan mengira keluarannya adalah hasil baru.

Kapan wildcard tepat digunakan?

Untuk direktori ini secara persis, perintah berikut dapat digunakan sebagai alternatif dari daftar sumber eksplisit:

clang++ -std=c++17 ./*.cpp -o desk-demo && ./desk-demo

Shell mengembangkan ./*.cpp menjadi file-file yang cocok di direktori saat ini. Perintah ini tidak mencari subdirektori atau menyertakan file berakhiran .cc atau .cxx. Jika mencantumkan jalur secara eksplisit, apit jalur yang mengandung spasi dengan tanda kutip.

Gunakan wildcard hanya selama setiap file yang cocok memang termasuk dalam eksekutabel ini. Jika nanti Anda menambahkan program demonstrasi terpisah dengan main sendiri, atau implementasi alternatif khusus platform, gunakan daftar sumber eksplisit. Di sini, “semua file implementasi” berarti semua file yang diperlukan oleh satu target, dengan setiap definisi yang diperlukan disediakan sebagaimana mestinya.

Kompilasi file objek terlebih dahulu, lalu tautkan

Memisahkan tahap-tahap ini memudahkan Anda melihat apakah kegagalan terjadi saat mengompilasi satu file sumber atau saat menggabungkan program. Dengan empat file dan direktori kerja yang sama, jalankan:

(
    set -e
    mkdir -p build
    clang++ -std=c++17 -c main.cpp -o build/main.o
    clang++ -std=c++17 -c label.cpp -o build/label.o
    clang++ -std=c++17 -c totals.cpp -o build/totals.o
    clang++ build/main.o build/label.o build/totals.o -o build/desk-demo
    ./build/desk-demo
)

Opsi -c menghasilkan file objek tanpa melakukan penautan. Pemanggilan kompilator terakhir menerima ketiga file objek lalu menautkannya menjadi eksekutabel. Ini mengikuti tahap kompilasi yang didokumentasikan Clang. set -e pada subshell menghentikan rangkaian perintah berurutan ini jika ada perintah yang gagal.

Gunakan juga nama file objek yang eksplisit. Menautkan semua objek dalam direktori build yang digunakan kembali dapat secara tidak sengaja menyertakan file usang dari versi program sebelumnya. Jika dua file sumber di folder berbeda memiliki nama dasar yang sama, berikan jalur objek yang berbeda alih-alih menulis keduanya ke nama file keluaran yang sama.

Jika Anda hanya mengubah label.cpp, Anda dapat mengompilasi ulang objek tersebut dan mengulangi perintah penautan. Jika Anda mengubah desk.hpp, bangun ulang setiap sumber yang menyertakannya. Alur kerja manual ini mengharuskan Anda melacak hubungan tersebut; ketika proyek berkembang, sistem build akan lebih praktis.

Diagnosis kegagalan sebelum mengubah perintah build

Urutan berikut menerapkan tahap kompilasi dan aturan definisi C++ pada contoh ini. Redaksi diagnostik yang tepat berbeda-beda, bergantung pada toolchain yang terpasang.

Gejala Hal pertama yang perlu diperiksa Langkah berikutnya yang tepat
Header dilaporkan tidak ditemukan Ejaan include dan lokasi header Kembalikan tata letak yang ditunjukkan atau konfigurasi direktori pencarian header yang benar.
make_label atau total_items tidak terdefinisi saat penautan Apakah implementasinya menjadi masukan Tambahkan file .cpp yang hilang atau objek hasil kompilasinya ke proses penautan.
Fungsi tetap tidak terdefinisi meskipun file-nya disertakan Tanda tangan deklarasi dan definisi Bandingkan namespace, tipe parameter, dan kualifier; sertakan header bersama dalam implementasi.
Simbol duplikat muncul Masukan implementasi yang berulang Hapus objek yang berulang, file .cpp yang disertakan, atau definisi alternatif yang tidak disengaja.
main tidak terdefinisi atau terduplikasi File titik masuk Sertakan main.cpp yang dimaksud tepat satu kali dan kecualikan titik masuk milik program lain.
Simbol pustaka C++ tidak terdefinisi Driver penautan akhir Gunakan clang++ agar pustaka runtime C++ disertakan.

Sebagai contoh, mengompilasi hanya main.cpp dapat menerima pemanggilan fungsi karena header mendeklarasikan kedua fungsi. Penautan akhir tetap memerlukan definisinya. Menambahkan penyertaan header lain tidak menyediakan objek implementasi yang hilang. Persyaratan yang mendasarinya dijelaskan dalam aturan satu definisi C++.

Hindari mengatasi simbol yang hilang dengan menyertakan label.cpp di dalam main.cpp sekaligus mengompilasi label.cpp secara terpisah. Cara itu menyediakan definisi fungsi biasa yang sama melalui dua unit translasi. Header bersama dan masukan implementasi terpisah sudah menyediakan struktur yang dimaksud.

Untuk memeriksa perintah tool tanpa menjalankan build, gunakan opsi -### yang didokumentasikan Clang. Apit opsi itu dengan tanda kutip agar shell meneruskannya apa adanya:

clang++ '-###' -std=c++17 main.cpp label.cpp totals.cpp -o desk-demo

Cara ini menampilkan pemanggilan kompilator dan linker yang dipilih driver, sehingga membantu membedakan perintah yang Anda maksud dari perintah yang akan dijalankan toolchain. Lihat Menyusun toolchain lengkap.

Jaga agar arsitektur, platform, dan ABI tetap kompatibel

Menambahkan semua file sumber tidak dapat memperbaiki masukan biner yang tidak kompatibel. Untuk build lokal, mulailah dengan satu toolchain Apple yang terpasang dan bangun ulang sumber program secara bersamaan. Saat menambahkan pustaka yang sudah dikompilasi sebelumnya, periksa platform dan arsitektur yang didukungnya sebelum mengubah daftar sumber.

Untuk build universal opsional, Apple mendokumentasikan cara mengompilasi dan menautkan secara terpisah untuk setiap arsitektur, lalu menggabungkan eksekutabel yang telah selesai dengan lipo. Dengan toolchain dan SDK yang mendukung kedua target, contoh yang sama dapat dibangun sebagai berikut. Target deployment macOS 11 di bawah ini adalah pilihan ilustratif:

(
    set -e
    mkdir -p build
    clang++ -std=c++17 -target arm64-apple-macos11 \
        main.cpp label.cpp totals.cpp -o build/desk-arm64
    clang++ -std=c++17 -target x86_64-apple-macos11 \
        main.cpp label.cpp totals.cpp -o build/desk-x86_64
    lipo -create build/desk-arm64 build/desk-x86_64 \
        -output build/desk-universal
    lipo -archs build/desk-universal
)

Setiap dependensi tertaut harus menyediakan masukan yang kompatibel untuk build arsitektur terkait. Biner iOS tidak berubah menjadi biner macOS hanya karena keduanya menggunakan arm64. Apple menjelaskan irisan arsitektur, perbedaan platform, dan pemeriksaan eksekutabel dalam Membangun biner macOS universal.

Versi bahasa C++ juga berbeda dari kompatibilitas biner, atau ABI. Mencocokkan -std=c++17 saja tidak membuktikan bahwa pustaka yang dibangun secara terpisah memiliki tata letak dan konfigurasi yang kompatibel. LLVM mendokumentasikan flag konfigurasi pustaka yang memengaruhi ABI dan menyatakan bahwa pengguna sebaiknya tidak mengatur sendiri flag vendor tersebut. Untuk tugas ini, mempertahankan konfigurasi pustaka normal dari toolchain adalah pilihan yang praktis. Lihat Jaminan ABI libc++.

Terapkan daftar sumber yang sama di Xcode atau proyek Swift campuran

Di target Xcode yang sudah ada, pilih target dan buka Build Phases. Periksa Compile Sources untuk memastikan file implementasi yang diperlukan oleh eksekutabel tersebut tercantum. Tambahkan dependensi pustaka kustom melalui Link Binary with Libraries. Apple mendokumentasikan tanggung jawab terpisah ini dalam Menyesuaikan fase build target.

Untuk contoh ini, daftar sumber harus mencakup main.cpp, label.cpp, dan totals.cpp. Menambahkan header ke proyek tidak menggantikan kompilasi file implementasi.

Jika aplikasi di sekitarnya menggunakan Swift, perintah C++ di atas hanya mencakup program C++. Jalur yang didokumentasikan Swift mengharuskan interoperabilitas C++ diaktifkan agar API dapat diakses lintas bahasa; hanya fitur bahasa yang didukung yang dapat melewati batas tersebut. Lihat Mencampur Swift dan C++.

Kode campuran Swift dan C++ harus menggunakan pustaka standar C++ yang sama; di platform Apple, Swift menggunakan libc++. Swift juga mendokumentasikan batasan kompilator dan versi bahasa, termasuk persyaratan C++14 atau yang lebih baru untuk interoperabilitas. Periksa persyaratan tersebut sebelum menganggap pustaka yang ada sebagai dependensi yang kompatibel. Lihat Fitur yang Didukung dan Batasan Interoperabilitas C++.

Untuk tugas Terminal lokal, mulailah dengan build tiga sumber yang eksplisit. Jika gagal, identifikasi tahap yang gagal, periksa definisi yang hilang atau berulang, dan pastikan masukan binernya kompatibel. Setelah perintah tersebut menghasilkan keluaran yang diharapkan, daftar sumbernya dapat menjadi dasar konkret untuk build yang lebih besar.

Sumber