2012-06-20 16 views
5

que estaba teniendo un problema extraño que redujo a la siguiente caso de prueba:Acceso globales estáticas en una función en línea

inl.h:

inline const char *fn() { return id; } 

a.cc:

#include <stdio.h> 

static const char *id = "This is A"; 

#include "inl.h" 

void A() 
{ 
    printf("In A we get: %s\n", fn()); 
} 

b.cc:

#include <stdio.h> 

static const char *id = "This is B"; 

#include "inl.h" 

void B() 
{ 
    printf("In B we get: %s\n", fn()); 
} 

extern void A(); 

int main() 
{ 
    A(); 
    B(); 
    return 0; 
} 

Ahora cuando compilo esto con g++ -O1 a.cc b.cc parece que funciona correctamente. Me sale:

In A we get: This is A 
In B we get: This is B 

pero si compilo con g++ -O0 a.cc b.cc me sale:

In A we get: This is A 
In B we get: This is A 

Tenga en cuenta que en realidad estoy tratando de utilizar la semántica C11 aquí, pero estoy usando g ++ como gcc no lo hace soporte C11 todavía.

Ahora, hasta donde puedo ver, mirando tanto la especificación C11 como la especificación C++ (las especificaciones C++ 11 y anteriores - la semántica de las variables globales en línea y estáticas no parece haber cambiado), debería hacer lo que quiero, y la falla al usar -O0 es un error en gcc.

¿Es esto correcto, o hay algo en algún lugar de la especificación que me falta que haría este comportamiento indefinido?

Editar

La respuesta común parece afirmar que fn necesidades a ser declarados como static para que esto funcione. Pero de acuerdo con 6.7.4.6 de la especificación C99 (6.7.4.7 en la especificación C11 - no está seguro acerca de la especificación C++):

Si todas las declaraciones ámbito de archivo para una función en una unidad de traducción incluyen la especificador de función en línea sin extern, entonces la definición en esa unidad de traducción es una definición en línea. Una definición en línea no proporciona una definición externa para la función, y no prohíbe una definición externa en otra unidad de traducción.

Así que como no hay explícito extern aquí, estas deberían ser dos funciones en línea independientes sin interacción entre ellas. No se requiere static explícito.

El uso de una static explícita soluciona el problema de C, pero no funciona para las funciones de miembro en línea de C++, ya que la palabra clave static tiene un significado completamente diferente en ese caso.

+0

Fuera de interés, ¿se replica el comportamiento si reemplaza las inclusiones con la definición de función en línea? Si el preprocesador se está ejecutando como primer pase independiente como me imagino, debería poder simplificar el caso de prueba. –

+0

Por cierto: ¿qué versión de gcc? –

+0

gcc 4.5.3 y 4.6.1 ambos se comportan de esta manera. –

Respuesta

8

Has violado la regla de una definición. La función no estática fn se define de manera diferente en sus dos unidades de traducción. Uno se une con la variable id definida en a.cc, donde como el otro se une con la variable id en b.cc. Las definiciones son textualmente idénticas, pero eso no es suficiente para satisfacer la regla de una definición, incluso con la excepción establecida para las funciones declaradas inline, por lo que obtiene un comportamiento indefinido.

Está utilizando un compilador de C++, no un compilador de C, por lo que cualquier C11 dice que es irrelevante con respecto al comportamiento que exhibe su programa de C++. En C++ 11, el estándar (en § 3.2/5) parece indicar la regla sobre cómo fn se permite hacer referencia a id (énfasis y elipses mío):

No puede haber más de una definición de una & hellip; función en línea con enlace externo (7.1.2) & hellip; en un programa siempre que cada definición aparezca en una unidad de traducción diferente, y siempre que las definiciones cumplan con los siguientes requisitos. Dada una entidad tal llamado D se define en más de una unidad de traducción, a continuación,

  • cada definición de D consistirá en la misma secuencia de tokens; y
  • en cada definición de D, nombres correspondientes, miraron según 3.4, hará referencia a un entidad definida dentro de la definición de D, o se refieren a la misma entidad, después de la sobrecarga de de resolución (13.3) y después de la coincidencia de la plantilla parcial especialización (14.8.3), excepto que un nombre puede referirse a un objeto const con enlace interno o sin vinculación si el objeto tiene el mismo tipo literal en todas las definiciones de D, y el objeto se inicializa con una expresión constante (5.19), y el valor (pero no la dirección) del 012 Se utiliza el objeto, y el objeto tiene el mismo valor en todas las definiciones de D; y
  • & hellip;

Sus definiciones de fn consisten en la misma secuencia de tokens, pero se refieren a id, que no se define dentro de D, no es la misma entidad en ambas unidades de traducción, y no tiene el mismo valor en todas las definiciones. No veo ninguna disposición en el estándar C++ para una función en línea que adquiera enlaces internos implícitamente. C++ 11 § 7.1.1/7 dice esto:

Un nombre declarado en un ámbito espacio de nombres sin almacenamiento de clase-especificador tiene enlazado externo a menos que tenga vinculación interna debido a una declaración anterior y siempre no está declarado const.

Si tienes tu comportamiento esperado en ciertos niveles de optimización, o de ciertas versiones de ciertos compiladores, a continuación, se acaba de encontrar la versión particularmente nefasto de un comportamiento indefinido, donde las cosas parecen funcionar a pesar de estar mal.

+0

Pero de acuerdo con la especificación, las dos unidades de compilación dan dos definiciones internas independientes de la función, ninguna de las cuales debe ser externamente visible, y que no debe interactuar de ninguna manera ... –

+0

@ChrisDodd Proporciona dos definiciones independientes de la función , __ambas de las cuales__ son externamente visibles, y el enlazador es libre de quejarse en voz alta sobre las definiciones duplicadas, o, como parece que este caso hizo, descartarlo en silencio, suponiendo que son idénticas (aunque no lo sean). Declarar 'fn()' como 'static' los haría internos no visibles externamente. – twalberg

+0

@twalberg: consulte la cita de especificaciones anterior: "no proporciona una definición externa de la función" –

3

Porque fn() no está declarado static, como señala @RobKennedy, te estás aventurando en la tierra de UB. En este caso específico, -O0 probablemente deshabilita la alineación, lo que significa que una de las versiones emitidas no en línea de la función se mantendrá y la otra descartará, y ambas llamadas serán a la única versión no en línea. Si la versión A siempre se guarda puede depender del orden en el que especifique sus archivos en la línea de comando o de cualquier otra cosa.-O1 probablemente incluido en línea, en cuyo caso, incluso si hay una copia no en línea de la función emitida, las dos llamadas aún pueden estar en línea, lo que da los resultados esperados (erróneamente).

Cuestiones relacionadas