Encontré una curiosidad al compilar con clang (en una MacBook, si ayuda). Supongamos que tengo dos archivos:
bla.c
int *p;C Principal
#include <stdio.h> extern int *p; int main() { printf("%p\n", p); return 0; }Si compilo con
clang blah.c main.ctodo sale bien Sin embargo, si lo hago
clang -c blah.c ar rcs libblah.a blah.o clang main.c libblah.aMe sale un error del enlazador:
Undefined symbols for architecture x86_64: "_p", referenced from: _main in test-4bf0d6.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation)Curiosamente, si inicializo la variable en blah.c,
#include <stddef.h> int *p = NULL;el error desaparece.
Además, compilar con gcc no produce este comportamiento. ¿Qué está pasando exactamente con el sonido metálico aquí?
Aquí está el resultado de clang --version :
Apple clang version 13.0.0 (clang-1300.0.29.30) Target: x86_64-apple-darwin21.2.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin¿Qué está pasando exactamente con el sonido metálico aquí?
TL;DR: Tu Clang tiene un error. Probablemente pueda evitarlo sin modificar su código agregando -fno-common a sus opciones de compilación.
Ambas variaciones de su código son correctas y, en lo que respecta a la especificación del lenguaje C, tienen el mismo significado. En mi máquina Linux, GCC 8.5 y Clang 12 aceptan ambas variaciones y construyen con éxito ejecutables que funcionan, ya sea que blah.o esté vinculado directamente o desde una biblioteca.
Pero si usa nm para examinar la biblioteca construida con y sin el inicializador para p , es probable que obtenga una pista sobre lo que está sucediendo. Sin un inicializador, veo (con cualquiera de los compiladores) que p tiene el tipo 'C' (común). Con un inicializador (a nulo), veo que tiene tipo 'B' (BSS).
Eso refleja un comportamiento tradicional de las implementaciones de Unix C: fusionar múltiples definiciones del mismo símbolo siempre que no se defina más de una con un inicializador explícito. Esa es una extensión del estándar C, ya que el lenguaje requiere que haya exactamente una definición de cada símbolo externo al que hace referencia un programa. Entre otras cosas, esa extensión cubre el error común de omitir extern de las declaraciones de variables en los encabezados, siempre que el encabezado no especifique un inicializador.
Para implementar eso, la cadena de herramientas debe distinguir entre los símbolos definidos con un inicializador explícito y los definidos sin él, y ahí es donde entra (para C) el tipo de símbolo "común": se usa para transmitir un símbolo que está definido, pero sin un inicializador explícito. El comportamiento típico del enlazador sería tratar todos esos símbolos como indefinidos si uno de los objetos que se vinculan tiene una definición para ese símbolo con un tipo diferente, o bien tratar todos menos uno de ellos como indefinidos y el otro como si tuvieran el tipo B. (lo que implica inicialización predeterminada).
Pero la cadena de herramientas de desarrollo de MacOS parece haber generado un error. En su ejemplo, no reconoce erróneamente el símbolo de tipo C como una definición viable cuando aparece en una biblioteca. El problema puede estar en el front-end de Clang o en el enlazador del sistema, o en una combinación de ambos. Quizás esto llegó junto con el reciente endurecimiento de Apple (y posterior relajamiento) de la configuración de conformidad predeterminada del compilador.
Probablemente pueda solucionar este problema agregando --fno-common a los indicadores del compilador de C. GCC y Clang aceptan eso para deshabilitar la combinación de símbolos descrita anteriormente y, al menos en mi máquina, ambos implementan eso emitiendo el símbolo como tipo B cuando se define sin un inicializador explícito, como si hubiera sido inicializado explícitamente a un puntero nulo. Tenga en cuenta, sin embargo, que esto romperá cualquier código que actualmente dependa de ese comportamiento de fusión.