Para crear un ejecutable nativo para Mac a partir de varios archivos C++, pasa todos los archivos de implementación necesarios a clang++ o compílalos en archivos objeto y enlázalos entre sí. Esta guía crea un pequeño programa para Terminal y explica cómo diagnosticar definiciones faltantes.

Preparar las herramientas de compilación de Mac

Usa este flujo de trabajo para un programa pequeño en C++ que quieras ejecutar localmente en Terminal. Apple proporciona el compilador y el SDK de macOS mediante Xcode o el paquete independiente de herramientas de línea de comandos. Si ya tienes Xcode instalado, no necesitas el paquete aparte. De lo contrario, ejecuta el siguiente comando y completa el cuadro de diálogo de instalación. Consulta Instalar las herramientas de línea de comandos de Apple.

xcode-select --install

Después de la instalación, comprueba que el compilador esté disponible:

clang++ --version

Usa clang++ para el enlace final, incluso cuando las entradas ya sean archivos objeto compilados. La documentación de Clang recomienda explícitamente este controlador para programas en C++, ya que el modo del lenguaje determina qué bibliotecas de tiempo de ejecución enlaza automáticamente. Consulta Ensamblar una cadena de herramientas completa.

Crear un programa C++ completo de tres archivos

El ejemplo ficticio cuenta objetos en tres bandejas de escritorio y da formato al resultado. Tiene un punto de entrada y dos archivos de implementación complementarios. Crea un directorio de trabajo nuevo y vacío y guarda estos cuatro archivos directamente en él:

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

Guarda las declaraciones compartidas como 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

Guarda el punto de entrada como main.cpp:

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

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

Guarda la implementación del formato como label.cpp:

#include "desk.hpp"

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

Guarda la implementación del recuento como totals.cpp:

#include "desk.hpp"

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

Cada implementación incluye el mismo encabezado, lo que permite al compilador comprobar la definición de la función frente a la declaración compartida. La protección del encabezado evita inclusiones repetidas dentro de una misma unidad de traducción. Mantén los cuerpos de estas funciones ordinarias y no inline en sus archivos de implementación; C++ impone requisitos de definición en todo el programa mediante su regla de una definición.

Compilar y enlazar con un solo comando

Abre Terminal en el directorio que contiene los cuatro archivos y ejecuta:

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

El resultado esperado es:

Desk items: 12

El comando indica los tres archivos de implementación y establece el nombre del archivo de salida con -o. Como no incluye ninguna opción que detenga el proceso en una etapa intermedia, Clang compila las entradas e invoca el enlazador. La opción explícita -std=c++17 selecciona la versión del lenguaje en lugar de depender del valor predeterminado del compilador. Estas opciones se documentan en la guía de comandos del compilador Clang.

El shell ejecuta el ejecutable solo si la compilación tiene éxito, porque los comandos están unidos con &&. Esto importa al volver a compilar: si la compilación falla, no se debe ejecutar después por accidente un ejecutable antiguo y confundir su salida con el nuevo resultado.

¿Cuándo es apropiado usar un comodín?

Para este directorio exacto, lo siguiente es una alternativa a la lista explícita de archivos fuente:

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

El shell expande ./*.cpp a los archivos coincidentes del directorio actual. No busca en carpetas anidadas ni incluye archivos que terminen en .cc o .cxx. Si indicas explícitamente rutas que contienen espacios, ponlas entre comillas.

Usa el comodín solo mientras todos los archivos coincidentes pertenezcan a este ejecutable. Si más adelante añades otro programa de demostración con su propio main o una implementación alternativa específica de una plataforma, mantén una lista explícita de archivos fuente. En este caso, «todos los archivos de implementación» significa todos los archivos necesarios para un mismo destino, con cada definición requerida suministrada de la forma adecuada.

Compilar primero los archivos objeto y luego enlazarlos

Separar las etapas facilita determinar si el fallo ocurre al compilar un archivo fuente o al combinar el programa. Con los mismos cuatro archivos y el mismo directorio de trabajo, ejecuta:

(
    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
)

La opción -c genera un archivo objeto sin enlazar. La invocación final del compilador recibe los tres objetos y enlaza el ejecutable. Esto sigue las etapas de compilación documentadas por Clang. set -e, dentro del subshell, detiene esta secuencia lineal si falla un comando.

Usa también nombres de objeto explícitos en este caso. Enlazar todos los objetos de un directorio de compilación reutilizado puede incluir por accidente un archivo obsoleto de una versión anterior del programa. Si dos archivos fuente de carpetas distintas comparten el mismo nombre base, asigna a sus objetos rutas distintas en lugar de escribir ambos con el mismo nombre de salida.

Si solo cambias label.cpp, puedes volver a compilar ese objeto y repetir el comando de enlace. Si cambias desk.hpp, recompila todos los archivos fuente que lo incluyan. Este flujo de trabajo manual requiere que lleves un seguimiento de esas relaciones; cuando el proyecto crezca, será más práctico usar un sistema de compilación.

Diagnosticar el fallo antes de cambiar el comando de compilación

La siguiente secuencia aplica las etapas de compilación y las reglas de definición de C++ a este ejemplo. La redacción exacta de los mensajes de diagnóstico varía según la cadena de herramientas instalada.

Síntoma Qué comprobar primero Siguiente paso adecuado
Se indica que falta un encabezado La ruta de inclusión y la ubicación del encabezado Restablece la estructura mostrada o configura el directorio correcto de búsqueda de encabezados.
make_label o total_items no está definido durante el enlace Si su implementación figura entre las entradas Añade al enlace el archivo .cpp que falta o su objeto compilado.
La función sigue sin estar definida pese a incluir su archivo Las firmas de la declaración y la definición Compara los espacios de nombres, los tipos de parámetros y los calificadores; incluye el encabezado compartido en la implementación.
Aparecen símbolos duplicados Si hay entradas de implementación repetidas Elimina el objeto repetido, el .cpp incluido o una definición alternativa no intencionada.
main no está definido o aparece duplicado Los archivos del punto de entrada Incluye exactamente una vez el main.cpp previsto y excluye los puntos de entrada de otros programas.
Hay símbolos indefinidos de la biblioteca C++ El controlador del enlace final Usa clang++ para que se proporcionen las bibliotecas de tiempo de ejecución de C++.

Por ejemplo, compilar solo main.cpp puede aceptar las llamadas porque el encabezado declara ambas funciones. El enlace final sigue necesitando sus definiciones. Añadir otra inclusión del encabezado no proporciona el objeto de implementación que falta. La regla de una definición de C++ explica los requisitos subyacentes.

Evita solucionar símbolos faltantes incluyendo label.cpp dentro de main.cpp y compilando también label.cpp de forma independiente. Así, la misma definición de una función ordinaria se proporciona a través de dos unidades de traducción. El encabezado compartido y los archivos de implementación separados ya ofrecen la estructura prevista.

Para inspeccionar los comandos de las herramientas sin ejecutar la compilación, usa la opción documentada de Clang -###. Ponla entre comillas para que el shell la pase intacta:

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

Esto muestra las invocaciones del compilador y del enlazador que selecciona el controlador, lo que ayuda a distinguir el comando que pretendías ejecutar del que ejecutaría la cadena de herramientas. Consulta Ensamblar una cadena de herramientas completa.

Mantener la compatibilidad de arquitectura, plataforma y ABI

Añadir todos los archivos fuente no puede reparar entradas binarias incompatibles. Para una compilación local, empieza con una sola cadena de herramientas de Apple instalada y recompila juntas las fuentes del programa. Cuando añadas bibliotecas precompiladas, comprueba las plataformas y arquitecturas compatibles antes de cambiar las listas de archivos fuente.

Para una compilación universal opcional, Apple documenta compilar y enlazar por separado para cada arquitectura y, después, combinar los ejecutables resultantes con lipo. Si la cadena de herramientas y el SDK admiten ambos destinos, el mismo ejemplo se puede compilar así. Los destinos de macOS 11 que aparecen a continuación son opciones ilustrativas:

(
    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
)

Cada dependencia enlazada debe proporcionar una entrada compatible para la compilación de la arquitectura correspondiente. Un binario de iOS no se convierte en un binario de macOS por el simple hecho de que ambos usen arm64. Apple explica las secciones de arquitectura, las diferencias entre plataformas y la inspección de ejecutables en Crear un binario universal de macOS.

La versión del lenguaje C++ también es distinta de la compatibilidad binaria, o ABI. Que coincida -std=c++17 por sí solo no garantiza que las bibliotecas compiladas por separado tengan configuraciones y disposiciones compatibles. LLVM documenta las opciones de configuración de la biblioteca que afectan a la ABI y señala que los usuarios no deben establecer por su cuenta esas opciones específicas del proveedor. Para esta tarea, lo más práctico es mantener la configuración normal de la biblioteca de la cadena de herramientas. Consulta Garantías de ABI de libc++.

Aplicar la misma lista de archivos fuente en Xcode o en un proyecto mixto con Swift

En un destino de Xcode existente, selecciona el destino y abre Build Phases. Comprueba en Compile Sources que estén los archivos de implementación necesarios para ese ejecutable. Añade las dependencias de bibliotecas personalizadas mediante Link Binary with Libraries. Apple documenta estas responsabilidades por separado en Personalizar las fases de compilación de un destino.

Para el ejemplo, la lista de archivos fuente debe incluir main.cpp, label.cpp y totals.cpp. Añadir el encabezado al proyecto no sustituye la compilación de las implementaciones.

Si la aplicación que lo contiene usa Swift, los comandos puros de C++ anteriores solo abarcan el programa en C++. La ruta documentada por Swift requiere activar la interoperabilidad con C++ para exponer API entre los lenguajes; solo se pueden usar en esa frontera las funciones del lenguaje compatibles. Consulta Combinar Swift y C++.

El código mixto de Swift y C++ debe usar la misma biblioteca estándar de C++; en las plataformas de Apple, Swift usa libc++. Swift también documenta restricciones sobre el compilador y las versiones del lenguaje, incluido el requisito de C++14 o posterior para la interoperabilidad. Comprueba esos requisitos antes de considerar compatible una biblioteca existente. Consulta Funciones y restricciones compatibles de la interoperabilidad con C++.

Para la tarea local en Terminal, empieza con la compilación explícita de tres archivos fuente. Si falla, identifica la etapa que produjo el fallo, comprueba si falta alguna definición o está repetida y verifica que las entradas binarias sean compatibles. Una vez que el comando produzca el resultado esperado, tendrás una lista concreta de archivos fuente que podrás trasladar a una compilación más grande.

Fuentes