C/C++ advierte o prohíbe la concatenación de cadenas literales

6 minutos de lectura

avatar de usuario de gozag
gozag

¿Hay alguna manera de advertir o prohibir concatenaciones de cadenas literales como:

const char *a = "foo" " bar";

Pasé horas buscando un error en una gran matriz estática que tenía

const char * a[] = {"foo" "bar"};

en lugar de

const char * a[] = {"foo", "bar"};

  • Simpatizo con tu dolor. Probablemente todos hemos estado allí. Pero no querría prohibir la concatenación de cadenas por completo, porque gran parte del código depende de ello deliberadamente.

    – Steve cumbre

    19 de mayo a las 11:44


  • Uso esto cuando tengo una cadena de registro muy alta, por lo que una advertencia de este tipo sería algo molesto para mí. Dado que existe un uso válido de esta función, no creo que haya una manera fácil de automatizar la detección de este error tipográfico. Debe asegurarse de que el código esté bien probado.

    – Marek R.

    19 de mayo a las 11:47


  • @MarekR Sí, no es infalible. pero no tiene que ser. Es más importante no perderse ninguno. En la práctica, su ejemplo es probablemente muy raro. Otra cosa a verificar son las líneas que terminan con ", posibles espacios en blanco al final. Esos pueden ser atrapados con grep '"\s*$' Si es deseado.

    – Tom Karzes

    19 de mayo a las 12:17

  • La concatenación de cadenas literales es lógica fase 6 en el proceso de compilación, que ocurre antes de la tokenización. Probablemente no puedas hacer nada sobre el problema.

    – Richard Critten

    19 de mayo a las 12:17


  • Algunos candidatos (y/o puntos de partida): ¿Hay un indicador GCC para detectar la concatenación de cadenas literales? (2015. “Recientemente arreglé un error… alguien olvidó un , después string3) y ¿Por qué permitir la concatenación de literales de cadena? (2010. “Recientemente me picó un insecto sutil… Olvidé el , después two)

    -Peter Mortensen

    21 de mayo a las 11:29

avatar de usuario de cigien
ciudadano

Clang tiene una advertencia -Concatenación de cadenas de caracteres que está diseñado explícitamente para detectar tales errores:

warning: suspicious concatenation of string literals in an array initialization; did you mean to separate the elements with a comma? [-Wstring-concatenation]
char const *a[]  = { "ok", "foo" "bar", "ok"};
                                 ^
                                ,

Esto no funcionará exactamente para el ejemplo de juguete que mostró porque necesita tener varios inicializadores y solo perder comas en un par de lugares, es decir:

// no warning
char const *b[]  = {"foo" "bar"};
// no warning
char const *c[]  = {"ok", "foo" "bar"};
// no warning
char const *d[]  = {"foo" "bar", "ok"};

Pero cuando tiene una gran cantidad de inicializadores en una matriz y solo comete un error tipográfico en un par de lugares, esto parece ideal.

Aquí está un manifestación.

GCC no parece tener una advertencia equivalente, pero hay una pedido para que se agregue.

Tenga en cuenta que esto solo funciona para la inicialización de matrices. tu ejemplo de

const char *x = "foo" " bar";

no será detectado por esta advertencia (o cualquier otra que yo sepa).

También tenga en cuenta que habilitar esta advertencia puede generar muchos falsos positivos, pero puede usarlo con moderación cuando intente detectar errores.

  • ¡También encontré uno para clang-tidy! clang.llvm.org/extra/clang-tidy/checks/bugprone/…

    – gozag

    19 de mayo a las 12:35

  • Me pregunto por qué no funciona aquí: godbolt.org/z/W4cevbenj

    – Marek R.

    19 de mayo a las 12:58

  • GCC generalmente advertirá sobre la concatenación de cadenas con -Wtraditional (Solo C), pero habilitar esa opción probablemente no sea recomendable.

    – nielsen

    19 de mayo a las 14:32

  • ¿Hay un indicador GCC para detectar la concatenación de cadenas literales?

    – phuclv

    21 de mayo a las 10:50

No precisamente. La concatenación de cadenas literales es una parte indispensable de la gramática C/C++ y tiene muchos casos de uso. Por lo tanto, se necesita algún tipo de esfuerzo y eso puede frustrar el objetivo de detectar un error garrafal.

Sin embargo, la concatenación de cadenas funciona de manera muy estricta en dos literales de cadena que aparecen uno tras otro con solo un espacio en blanco en el medio, por lo que romper el espacio en blanco provocará un error. Por ejemplo, en este caso podrías haber escrito:

const *char[] = {("foo") ("bar")};  // Error

lo que causaría un error mientras que la declaración prevista no lo haría:

const *char[] = {("foo"), ("bar")};  // OK

Entonces, en resumen, no puede tener alguna forma de decirle explícitamente al compilador que dos literales de cadena pueden concatenarse y fallar en todos los demás casos, por lo que tendrá que decirle al compilador explícitamente cuando un literal de cadena puede estar concatenado. no ser concatenado.

  • “… decirle al compilador explícitamente cuándo no se puede concatenar un literal de cadena…” que tal poner un , ¿entre ellos? Siento que hemos completado el círculo.

    – Richard Critten

    19 de mayo a las 12:19


  • @RichardCritten Sí, el punto principal es que creo que la solución que busca el OP no existe dentro de los servicios del compilador C/C++.

    – nielsen

    19 de mayo a las 12:24


  • Creo que la mayoría de los usos “indispensables” implican la concatenación de literales de cadena con macros, por ejemplo, las macros utilizadas en cadenas de formato printf. Es más raro que realmente necesite concatenar solo literales de cadena.

    – Barmar

    20 de mayo a las 23:04

  • Concateno literales de cadenas desnudos todo el tiempo sin que haya macros involucradas, simplemente porque me gusta envolver líneas de código. La solución propuesta de envolver cada cadena/elemento individual entre paréntesis es intrigante. A pesar de una experiencia bastante amplia escribiendo y leyendo código C y C++, no habría sabido de inmediato si esa sintaxis era válida. Ciertamente tiene sentido que lo sea, pero también me parece sospechoso. Me inclinaría a marcar esto en una revisión de código. Si bien es interesante y una posible solución al problema mencionado, como sugiere Richard, nos lleva al punto de partida.

    – Cody Gray – en huelga

    21 de mayo a las 7:10


  • @CodyGray: Descubrí que los paréntesis eran sorprendentes inicialmente, pero no me llevó mucho pensar por qué es válido: un literal de cadena es un objeto de tipo const char* (o posiblemente char* en C, se me olvida). Los paréntesis pueden aparecer en expresiones y evaluarse como la subexpresión envuelta. Una lista de inicializadores quiere una lista de const char* expresiones Tuve que pensarlo durante un par de segundos, pero si una base de código usara esto en todas partes, estaría acostumbrado. Entonces, la pregunta más importante es si este ruido sintáctico adicional para leer el código es peor que el posible problema.

    – Peter Cordes

    22 de mayo a las 4:33

Avatar de usuario de Juan
Juan

Cualquiera de las macros a continuación hace que sea imposible concatenar accidentalmente dos cadenas.

PPC (preprocesador C) las macros son increíbles en general. También es legal tener una coma final al final de una lista de elementos.

Puedes hacer algo como esto:

#define STRINGCOMMA(a) a,

const char *x[] = {
    STRINGCOMMA("foo")
    STRINGCOMMA("bar")
};

O incluso:

#define QUOTESTRINGCOMMA(a) #a,

const char *x[] = {
    QUOTESTRINGCOMMA(foo)
    QUOTESTRINGCOMMA(bar)};

La coma se agrega por usted, y sería ilegal que usted mismo lo hiciera accidentalmente.

Si está interesado, también es posible llevar este concepto más allá para permitir la creación de listas paralelas con los mismos argumentos, pero con un procesamiento diferente:

Macro X

#define VARLIST \
  DO(foo) \
  DO(bar)

#define DO(a) #a,
  const char *x[] = {
VARLIST
};
#undef DO

Esto sería útil si desea crear una lista de enumeraciones y una lista de cadenas, a partir de la misma lista de nombres.

Pasé horas encontrando un error en una gran matriz estática…

Bueno, puedes hacer esto:

char const * a [] = 
    { "foo"
    , "bar"
    , "baz"
    , "asdf"
    , "ghjk"
    };

¿Ha sido útil esta solución?