Pour créer un exécutable Mac natif à partir de plusieurs fichiers C++, transmettez à clang++ tous les fichiers d’implémentation requis, ou compilez-les en fichiers objets puis liez-les ensemble. Ce guide crée un petit programme Terminal et explique comment diagnostiquer les définitions manquantes.
Préparer les outils de compilation Mac
Suivez cette procédure pour un petit programme C++ que vous souhaitez exécuter localement dans Terminal. Apple fournit le compilateur et le SDK macOS avec Xcode ou avec le paquet autonome des outils en ligne de commande. Si Xcode est déjà installé, vous n’avez pas besoin du paquet séparé. Sinon, exécutez la commande suivante et terminez l’installation dans la boîte de dialogue. Consultez Installation des outils en ligne de commande d’Apple.
xcode-select --install
Après l’installation, vérifiez que le compilateur est disponible :
clang++ --version
Utilisez clang++ pour l’édition de liens finale, y compris lorsque vos entrées sont déjà des fichiers objets compilés. La documentation de Clang recommande explicitement ce pilote pour les programmes C++, car le mode de langage détermine les bibliothèques d’exécution qu’il lie automatiquement. Consultez Assembler une chaîne d’outils complète.
Créer un programme C++ complet en trois fichiers
L’exemple synthétique compte les objets posés sur trois plateaux de bureau et met le résultat en forme. Il comporte un point d’entrée et deux fichiers d’implémentation auxiliaires. Créez un nouveau répertoire de travail vide, puis enregistrez ces quatre fichiers directement dedans :
desk-demo/
desk.hpp
main.cpp
label.cpp
totals.cpp
Enregistrez les déclarations communes sous le nom 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
Enregistrez le point d’entrée sous le nom main.cpp :
#include "desk.hpp"
#include <iostream>
int main() {
const int count = total_items(3, 4);
std::cout << make_label(count) << '\n';
return 0;
}
Enregistrez l’implémentation de la mise en forme sous le nom label.cpp :
#include "desk.hpp"
std::string make_label(int count) {
return "Desk items: " + std::to_string(count);
}
Enregistrez l’implémentation du comptage sous le nom totals.cpp :
#include "desk.hpp"
int total_items(int trays, int per_tray) {
return trays * per_tray;
}
Chaque fichier d’implémentation inclut le même en-tête, ce qui permet au compilateur de vérifier que la définition de chaque fonction correspond à la déclaration commune. La garde d’inclusion évite les inclusions répétées au sein d’une même unité de traduction. Gardez ces corps de fonctions ordinaires, non inline, dans leurs fichiers d’implémentation ; en C++, les exigences relatives aux définitions s’appliquent à l’ensemble du programme selon la règle de définition unique.
Compiler et lier en une seule commande
Ouvrez Terminal dans le répertoire contenant les quatre fichiers et exécutez :
clang++ -std=c++17 main.cpp label.cpp totals.cpp -o desk-demo && ./desk-demo
Le résultat attendu est :
Desk items: 12
La commande nomme les trois fichiers d’implémentation et définit le nom du fichier de sortie avec -o. Comme elle ne comporte aucune option qui arrête le processus à une étape intermédiaire, Clang compile les entrées puis appelle l’éditeur de liens. L’option explicite -std=c++17 sélectionne la version du langage, au lieu de dépendre de la valeur par défaut du compilateur. Ces options sont décrites dans le guide des commandes du compilateur Clang.
Le shell n’exécute le programme que si la compilation réussit, car les commandes sont reliées par &&. C’est important lors d’une nouvelle compilation : après un échec, il ne faut pas lancer accidentellement un ancien exécutable et prendre sa sortie pour le nouveau résultat.
Quand utiliser un caractère générique ?
Dans ce répertoire précis, la commande suivante peut remplacer la liste explicite des sources :
clang++ -std=c++17 ./*.cpp -o desk-demo && ./desk-demo
Le shell développe ./*.cpp en fichiers correspondants du répertoire courant. Il ne parcourt pas les sous-répertoires et n’inclut pas les fichiers se terminant par .cc ou .cxx. Lorsque vous indiquez explicitement des chemins contenant des espaces, mettez chacun d’eux entre guillemets.
N’utilisez le caractère générique que tant que tous les fichiers correspondants appartiennent à cet exécutable. Si vous ajoutez ensuite un programme de démonstration distinct avec son propre main, ou une autre implémentation spécifique à une plateforme, conservez une liste explicite des sources. Ici, « tous les fichiers d’implémentation » désigne tous les fichiers nécessaires à une cible, chaque définition requise étant fournie de manière appropriée.
Compiler d’abord les fichiers objets, puis les lier
Séparer les étapes permet de voir plus facilement si un échec survient lors de la compilation d’un fichier source ou lors de l’assemblage du programme. Avec les mêmes quatre fichiers et le même répertoire de travail, exécutez :
(
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
)
L’option -c produit un fichier objet sans effectuer l’édition de liens. La dernière invocation du compilateur reçoit les trois objets et lie l’exécutable. Cette procédure suit les étapes de compilation documentées par Clang. Le set -e du sous-shell interrompt cette séquence linéaire si une commande échoue.
Utilisez aussi des noms d’objets explicites ici. Lier tous les objets d’un répertoire de compilation réutilisé peut inclure accidentellement un fichier obsolète issu d’une version précédente du programme. Lorsque deux fichiers source situés dans des dossiers différents ont le même nom de base, attribuez-leur des chemins d’objets distincts au lieu de leur donner le même nom de fichier de sortie.
Si vous ne modifiez que label.cpp, vous pouvez recompiler cet objet puis répéter la commande d’édition de liens. Si vous modifiez desk.hpp, recompilez toutes les sources qui l’incluent. Cette procédure manuelle vous oblige à suivre ces dépendances ; dès que le projet prend de l’ampleur, un système de compilation devient plus pratique.
Diagnostiquer l’échec avant de modifier la commande de compilation
La séquence suivante applique à cet exemple les étapes de compilation et les règles C++ relatives aux définitions. Le texte exact des messages varie selon la chaîne d’outils installée.
| Symptôme | Première vérification | Étape suivante appropriée |
|---|---|---|
| Un en-tête est signalé comme manquant | L’orthographe de l’inclusion et l’emplacement de l’en-tête | Rétablissez la structure présentée ou configurez le bon répertoire de recherche des en-têtes. |
make_label ou total_items n’est pas défini lors de l’édition de liens |
Vérifiez si son implémentation figure parmi les entrées | Ajoutez le fichier .cpp manquant ou son fichier objet compilé à la commande d’édition de liens. |
| La fonction reste indéfinie malgré l’inclusion de son fichier | Les signatures de la déclaration et de la définition | Comparez les espaces de noms, les types de paramètres et les qualificateurs ; incluez l’en-tête commun dans l’implémentation. |
| Des symboles dupliqués apparaissent | Les entrées d’implémentation répétées | Supprimez l’objet en double, le fichier .cpp inclus ou une définition alternative involontaire. |
main n’est pas défini ou est dupliqué |
Les fichiers de point d’entrée | Incluez exactement une fois le main.cpp voulu et excluez les points d’entrée des autres programmes. |
| Des symboles de bibliothèque C++ sont indéfinis | Le pilote utilisé pour l’édition de liens finale | Utilisez clang++ pour que les bibliothèques d’exécution C++ soient fournies. |
Par exemple, compiler uniquement main.cpp peut suffire à accepter les appels, car l’en-tête déclare les deux fonctions. L’édition de liens finale nécessite toujours leurs définitions. Ajouter une autre inclusion de l’en-tête ne fournit pas l’objet d’implémentation manquant. Les exigences sous-jacentes sont expliquées par la règle de définition unique du C++.
Évitez de corriger des symboles manquants en incluant label.cpp dans main.cpp tout en compilant aussi label.cpp de manière indépendante. Cela fournit la même définition de fonction ordinaire au moyen de deux unités de traduction. L’en-tête commun et les entrées d’implémentation séparées fournissent déjà la structure prévue.
Pour examiner les commandes des outils sans exécuter la compilation, utilisez l’option documentée -### de Clang. Mettez-la entre guillemets pour que le shell la transmette telle quelle :
clang++ '-###' -std=c++17 main.cpp label.cpp totals.cpp -o desk-demo
Cette option révèle les commandes du compilateur et de l’éditeur de liens choisies par le pilote, ce qui aide à distinguer la commande voulue de celle que la chaîne d’outils exécuterait. Consultez Assembler une chaîne d’outils complète.
Veiller à la compatibilité des architectures, des plateformes et de l’ABI
Ajouter tous les fichiers source ne peut pas réparer des entrées binaires incompatibles. Pour une compilation locale, commencez avec une seule chaîne d’outils Apple installée et recompilez ensemble les sources du programme. Si vous ajoutez des bibliothèques précompilées, vérifiez les plateformes et architectures prises en charge avant de modifier les listes de sources.
Pour créer un exécutable universel, Apple documente la compilation et l’édition de liens séparément pour chaque architecture, puis la fusion des exécutables obtenus avec lipo. Avec une chaîne d’outils et un SDK qui prennent en charge les deux cibles, le même exemple peut être compilé ainsi. Les cibles de déploiement macOS 11 ci-dessous sont données à titre d’illustration :
(
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
)
Chaque dépendance liée doit fournir une entrée compatible pour la compilation correspondant à l’architecture concernée. Un binaire iOS ne devient pas un binaire macOS simplement parce que les deux utilisent arm64. Apple explique les tranches d’architecture, les distinctions entre plateformes et l’inspection des exécutables dans Créer un binaire macOS universel.
La version du langage C++ est également distincte de la compatibilité binaire, ou ABI. Faire correspondre uniquement -std=c++17 ne garantit pas que des bibliothèques compilées séparément aient des configurations et des dispositions compatibles. LLVM documente les options de configuration de la bibliothèque qui affectent l’ABI et précise que les utilisateurs ne doivent pas définir eux-mêmes ces options du fournisseur. Pour cette tâche, conserver la configuration normale de la bibliothèque de la chaîne d’outils est le choix le plus pratique. Consultez Garanties d’ABI de libc++.
Utiliser la même liste de sources dans Xcode ou un projet mêlant Swift et C++
Dans une cible Xcode existante, sélectionnez la cible et ouvrez Build Phases. Vérifiez dans Compile Sources que figurent les fichiers d’implémentation nécessaires à l’exécutable. Ajoutez les dépendances de bibliothèques personnalisées via Link Binary with Libraries. Apple décrit ces responsabilités distinctes dans Personnaliser les phases de compilation d’une cible.
Pour l’exemple, la liste des sources doit tenir compte de main.cpp, label.cpp et totals.cpp. Ajouter l’en-tête au projet ne remplace pas la compilation des implémentations.
Si l’application qui entoure le programme utilise Swift, les commandes C++ ci-dessus ne couvrent que le programme C++. La procédure documentée par Swift exige d’activer l’interopérabilité C++ afin d’exposer des API entre les langages ; seules les fonctionnalités prises en charge peuvent franchir cette frontière. Consultez Mélanger Swift et C++.
Le code Swift et C++ mêlé doit utiliser la même bibliothèque standard C++ ; sur les plateformes Apple, Swift utilise libc++. Swift documente également les contraintes du compilateur et de la version du langage, notamment C++14 ou une version ultérieure pour l’interopérabilité. Vérifiez ces exigences avant de considérer une bibliothèque existante comme une dépendance compatible. Consultez Fonctionnalités prises en charge et contraintes de l’interopérabilité C++.
Pour la tâche locale dans Terminal, commencez par la compilation explicite des trois sources. En cas d’échec, identifiez l’étape concernée, recherchez la définition manquante ou répétée et vérifiez la compatibilité des entrées binaires. Une fois la commande exécutée avec le résultat attendu, vous disposerez d’une liste concrète de sources à reprendre dans une compilation plus vaste.
Sources
- Installation des outils en ligne de commande — Apple
- clang — le compilateur C, C++ et Objective-C de Clang — LLVM Project
- Assembler une chaîne d’outils complète — LLVM Project
- Règle de définition unique — C++ Working Draft
- Créer un binaire macOS universel — Apple
- Garanties d’ABI de libc++ — LLVM Project
- Personnaliser les phases de compilation d’une cible — Apple
- Mélanger Swift et C++ — Swift.org
- Fonctionnalités prises en charge et contraintes de l’interopérabilité C++ — Swift.org