El siguiente programa compila sin errores.
#include <stdio.h> char addr_a[8]; char addr_b[8]; unsigned long my_addr = (unsigned long)addr_b - 8; // PASS // unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a; // FAIL (error: initializer element is not constant) int main() { printf("%lx\n", my_addr); return 0; } Curiosamente, cuando configuro unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a el compilador arroja "error: el elemento inicializador no es constante".
Sé que los globales solo se pueden inicializar con una expresión constante. También sé que los tipos de expresiones constantes que se pueden usar en un inicializador para un global se especifican en la sección 6.6p7 del estándar C :
Se permite más libertad para expresiones constantes en inicializadores. Dicha expresión constante será, o se evaluará como, una de las siguientes:
- una expresión constante aritmética,
- una constante de puntero nulo,
- una constante de dirección, o
- una constante de dirección para un tipo de objeto completo más o menos una expresión de constante entera.
Tenga en cuenta que se permite una constante de dirección menos una constante entera, pero no una constante de dirección menos otra constante de dirección.
Pregunta:
¿Por qué el estándar C restringe las formas en que puede inicializar variables globales? ¿Qué impide que el estándar C acepte unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a ?
Por qué querrías esto?
Supongamos addr_a y addr_b representan el inicio y el final de la sección .text respectivamente. Un programa puede querer mapear la sección .text , que tiene tamaño (unsigned long)addr_b - (unsigned long)addr_a . El proyecto trustware-firmware-a hace esto en la etapa 2 del cargador de arranque (BL2). Consulte BL_CODE_END - BL_CODE_BASE , que se utiliza en arm_bl2_setup.c .
Los objetos con duración de almacenamiento estático (es decir, globales, más locales definidos como static ) solo se pueden inicializar con una expresión constante .
Los tipos de expresiones constantes que se pueden usar en un inicializador para dicho objeto se especifican en la sección 6.6p7 del estándar C :
Se permite más libertad para expresiones constantes en inicializadores. Dicha expresión constante será, o se evaluará como, una de las siguientes:
- una expresión constante aritmética,
- una constante de puntero nulo,
- una constante de dirección, o
- una constante de dirección para un tipo de objeto completo más o menos una expresión de constante entera.
Tenga en cuenta que se permite una constante de dirección más una constante entera, pero no una constante de dirección más otra constante de dirección.
De acuerdo, esto todavía no es exactamente lo que tiene, ya que tiene constantes de dirección convertidas en tipo entero. Así que revisemos 6.6p6 que define una expresión constante entera :
Una expresión de constante entera tendrá un tipo entero y solo tendrá operandos que sean constantes enteras, constantes de enumeración, constantes de caracteres, expresiones sizeof cuyos resultados sean constantes enteras, expresiones
_Alignofy constantes flotantes que sean los operandos inmediatos de las conversiones. Los operadores de conversión en una expresión constante entera solo convertirán tipos aritméticos en tipos enteros , excepto como parte de un operando en el operadorsizeofo_Alignof.
Este párrafo no permite convertir una dirección constante a un tipo entero como parte de una expresión constante entera, pero aparentemente esto parece ser compatible como una extensión.
¿Qué impide que el estándar C acepte my_addr largo sin firmar = (largo sin firmar)addr_a + (largo sin firmar)addr_b?
La razón subyacente es "¿Por qué alguien querría eso?" No tiene sentido agregar dos direcciones absolutas juntas; el resultado no es la dirección de nada en particular.
Por lo tanto, es una especie de cosa del huevo y la gallina. El lenguaje no lo admite porque es inútil, pero también porque los enlazadores existentes y los formatos de archivos de objetos no admiten dicha reubicación. Por ejemplo, para ELF en x86-64, consulte la Tabla 4.9 de psABI para obtener una lista de las reubicaciones admitidas y tenga en cuenta que no hay S+S . Y los enlazadores no lo admiten porque es inútil y porque el idioma no requiere que sea compatible.
Supongo que originalmente, las herramientas probablemente llegaron antes que el lenguaje (los primeros compiladores de C habrían usado enlazadores diseñados para programas de ensamblaje). Entonces, las herramientas originales probablemente no eran compatibles con esto, el lenguaje no vio la necesidad de exigir que lo hicieran y, con el tiempo, ninguno vio la necesidad de agregarlo.