¿Por qué (solo) algunos compiladores usan la misma dirección para cadenas literales idénticas?

92

https://godbolt.org/z/cyBiWY

Puedo ver dos 'some'literales en el código ensamblador generado por MSVC, pero solo uno con clang y gcc. Esto conduce a resultados totalmente diferentes de ejecución de código.

static const char *A = "some";
static const char *B = "some";

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

¿Alguien puede explicar la diferencia y las similitudes entre esos resultados de compilación? ¿Por qué clang / gcc optimiza algo incluso cuando no se solicitan optimizaciones? ¿Es este algún tipo de comportamiento indefinido?

También noto que si cambio las declaraciones a las que se muestran a continuación, clang / gcc / msvc no deja ninguna "some"en el código ensamblador. ¿Por qué el comportamiento es diferente?

static const char A[] = "some";
static const char B[] = "some";
Eugene Kosov
fuente
4
stackoverflow.com/a/52424271/1133179 Alguna buena respuesta relevante a una pregunta estrechamente relacionada, con comillas estándar.
luk32
1
@ luk32 Discuto las banderas del compilador que afectan esto aquí
Shafik Yaghmour
6
Para MSVC, la opción del compilador / GF controla este comportamiento. Consulte docs.microsoft.com/en-us/cpp/build/reference/…
Sjoerd
1
Para su información, esto también puede suceder para las funciones.
user541686

Respuestas:

109

Este no es un comportamiento indefinido, sino un comportamiento no especificado. Para cadenas literales ,

El compilador puede, pero no es obligatorio, combinar el almacenamiento para cadenas literales iguales o superpuestas. Eso significa que los literales de cadena idénticos pueden o no compararse igual cuando se comparan por puntero.

Eso significa que el resultado de A == Bpodría ser trueo false, del cual no deberías depender.

Desde el estándar, [lex.string] / 16 :

No se especifica si todos los literales de cadena son distintos (es decir, se almacenan en objetos que no se superponen) y si las evaluaciones sucesivas de un literal de cadena producen el mismo objeto o uno diferente.

Songyuanyao
fuente
36

Las otras respuestas explicaron por qué no puede esperar que las direcciones de puntero sean diferentes. Sin embargo, puede reescribir esto fácilmente de una manera que garantice eso Ay Bno se compare igual:

static const char A[] = "same";
static const char B[] = "same";// but different

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

La diferencia es que Ay Bahora son matrices de personajes. Esto significa que no son punteros y sus direcciones tienen que ser distintas, como tendrían que ser las de dos variables enteras. C ++ confunde esto porque hace que los punteros y las matrices parezcan intercambiables ( operator*y operator[]parecen comportarse de la misma manera), pero son realmente diferentes. Por ejemplo, algo como const char *A = "foo"; A++;es perfectamente legal, pero const char A[] = "bar"; A++;no lo es.

Una forma de pensar en la diferencia es que char A[] = "..."dice "dame un bloque de memoria y rellénalo con los caracteres ...seguidos de \0", mientras que char *A= "..."dice "dame una dirección en la que pueda encontrar los caracteres ...seguidos de \0".

tobi_s
fuente
8
Esta sería una respuesta aún mejor si pudiera explicar por qué es diferente.
Mark Ransom
Tenga en cuenta que *py p[0]no sólo "parecen comportarse igual", sino que por definición son idénticos (siempre que p+0 == psea ​​una relación de identidad porque 0es el elemento neutral en la suma de puntero-entero). Después de todo, p[i]se define como *(p+i). Sin embargo, la respuesta tiene un buen punto.
Peter - Reincorpora a Monica
typeof(*p)y typeof(p[0])son ambos, charpor lo que realmente no queda mucho que pueda ser diferente. Estoy de acuerdo en que "parece que se comportan igual" no es la mejor redacción, porque la semántica es muy diferente. Tu entrada me recordó la mejor forma de elementos de acceso de matrices de C ++: 0[p], 1[p], 2[p]etc. Así es como lo hacen los profesionales, al menos cuando quieren confundir a las personas que nacieron después de que el lenguaje de programación C.
tobi_s
Esto es interesante, y tuve la tentación de agregar un enlace a las preguntas frecuentes de C, pero me di cuenta de que hay muchas preguntas relacionadas, pero ninguna parece ir directamente al punto de esta pregunta aquí.
tobi_s
23

Si un compilador elige usar la misma ubicación de cadena para la implementación Ay Bdepende de ella. Formalmente, puede decir que el comportamiento de su código no está especificado .

Ambas opciones implementan correctamente el estándar C ++.

Betsabé
fuente
El comportamiento del código es lanzar una excepción o no hacer nada, elegido, antes de la primera vez que se ejecuta el código, de una manera no especificada . Eso no significa que el comportamiento en su conjunto no esté especificado, simplemente que el compilador puede seleccionar cualquiera de los comportamientos de la manera que considere adecuada antes de la primera vez que se observa el comportamiento.
supercat
3

Es una optimización para ahorrar espacio, a menudo llamada "agrupación de cadenas". Aquí están los documentos para MSVC:

https://msdn.microsoft.com/en-us/library/s0s0asdt.aspx

Por lo tanto, si agrega / GF a la línea de comando, debería ver el mismo comportamiento con MSVC.

Por cierto, probablemente no debería comparar cadenas a través de punteros como ese, cualquier herramienta de análisis estático decente marcará ese código como defectuoso. Debe comparar lo que apuntan, no los valores reales del puntero.

Paulm
fuente