¿Por qué un compilador no puede evitar importar un archivo de encabezado dos veces por sí mismo?

13

¡Nuevo en C ++! Entonces estaba leyendo esto: http://www.learncpp.com/cpp-tutorial/110-a-first-look-at-the-preprocessor/

Guardias de cabecera

Debido a que los archivos de encabezado pueden incluir otros archivos de encabezado, es posible terminar en una situación en la que un archivo de encabezado se incluye varias veces.

Entonces hacemos directivas de preprocesador para evitar esto. Pero no estoy seguro: ¿por qué el compilador no puede ... no importar lo mismo dos veces?

Dado que los protectores de encabezado son opcionales (pero aparentemente una buena práctica), casi me hace pensar que hay escenarios en los que desea importar algo dos veces. Aunque no puedo pensar en tal escenario en absoluto. ¿Algunas ideas?

Omega
fuente
En el compilador de MS hay una #pragma onceque le dice al compilador que solo incluya ese archivo una vez.
CodesInChaos

Respuestas:

27

Pueden, como lo muestran los nuevos idiomas que lo hacen.

Pero se tomó una decisión de diseño hace todos esos años (cuando el compilador de C era múltiples etapas independientes) y ahora para mantener la compatibilidad, el preprocesador debe actuar de cierta manera para asegurarse de que el código antiguo se compila como se esperaba.

Como C ++ hereda la forma en que procesa los archivos de encabezado de C, mantuvo las mismas técnicas. Estamos respaldando una antigua decisión de diseño. Pero cambiar la forma en que funciona es demasiado arriesgado y podría romper mucho código. Así que ahora tenemos que enseñar a los nuevos usuarios del lenguaje cómo usar los guardias de inclusión.

Hay un par de trucos con archivos de encabezado donde los incluyó deliberadamente varias veces (esto en realidad proporciona una característica útil). Sin embargo, si rediseñamos el paradigma desde cero, podríamos hacer que esta sea la forma no predeterminada de incluir archivos.

Martin York
fuente
7

De lo contrario, no sería tan expresivo, dado que optaron por mantener la compatibilidad con C y, por lo tanto, continuar con un preprocesador en lugar de un sistema de embalaje tradicional.

Una cosa que me viene a la mente es que tenía un proyecto que era una API. Tenía dos archivos de encabezado x86lib.hy x86lib_internal.h. Debido a que el interno era enorme, separé los bits "públicos" a x86lib.h para que los usuarios no tuvieran que reservar tiempo adicional para la compilación.

Esto introdujo un problema divertido con las dependencias, así que terminé teniendo un flujo que fue así en x86lib_internal

  1. Establecer definición interna del preprocesador
  2. Incluya x86lib.h (que fue inteligente para actuar de cierta manera cuando se definió interno)
  3. Haga algunas cosas e introduzca algunas cosas usadas en x86lib.h
  4. Establecer DESPUÉS de definir el preprocesador
  5. Incluya x86lib.h nuevamente (esta vez ignoraría todo excepto una porción DESPUÉS segregada que dependía de elementos de x86lib_internal

No diría que fue la mejor manera de hacerlo, pero logró lo que quería.

Earlz
fuente
0

Una dificultad con la exclusión automática de encabezado duplicado es que el estándar C es relativamente silencioso sobre el tema de lo que significa incluir nombres de archivos. Por ejemplo, suponga que el archivo principal que se está compilando contiene directivas #include "f1.h"y #include "f2.h", y los archivos encontrados para esas directivas contienen ambos #include "f3.h". Si f1.hy f2.hestán en directorios diferentes, pero se encontraron buscando rutas de acceso incluidas, entonces no estaría claro si las #includedirectivas dentro de esos archivos tenían la intención de cargar el mismo f3.harchivo o diferentes.

Las cosas empeoran aún más si se agregan las posibilidades de incluir archivos que incluyen rutas relativas. En algunos casos en los que los archivos de encabezado utilizan rutas relativas para las directivas de inclusión anidadas, y donde se desea evitar realizar cambios en los archivos de encabezado suministrados, puede ser necesario duplicar un archivo de encabezado en varios lugares dentro de la estructura de directorios de un proyecto. Aunque existen múltiples copias físicas de ese archivo de encabezado, deben considerarse semánticamente como si fueran un solo archivo.

Si la #pragma oncedirectiva permitiera seguir un identificador once, con la semántica de que el compilador debería omitir el archivo si el identificador coincide con uno de una #pragma oncedirectiva encontrada anteriormente , entonces la semántica no sería ambigua; un compilador que podría decir que una #includedirectiva cargaría el mismo #pragma oncearchivo etiquetado que uno anterior, podría ahorrar un poco de tiempo omitiendo el archivo sin abrirlo nuevamente, pero dicha detección no sería semánticamente importante ya que el archivo se omitiría si o no el nombre de archivo fue reconocido como una coincidencia. Sin embargo, no conozco ningún compilador que funcione de esa manera. Hacer que un compilador observe si un archivo coincide con el patrón #ifndef someIdentifier / #define someIdentifier / #endif [for that ifndef] / nothing followingy tratar tal cosa como equivalente al anterior #pragma once someIdentifiersisomeIdentifier permanece definido, es esencialmente tan bueno.

Super gato
fuente