int a [] = {1,2,}; Se permite una coma extraña. ¿Alguna razón en particular?

335

Tal vez no soy de este planeta, pero me parece que lo siguiente debería ser un error de sintaxis:

int a[] = {1,2,}; //extra comma in the end

Pero no lo es. Me sorprendió cuando este código se compiló en Visual Studio, pero aprendí a no confiar en el compilador MSVC en lo que respecta a las reglas de C ++, por lo que verifiqué el estándar y también está permitido por el estándar. Puedes ver 8.5.1 para las reglas gramaticales si no me crees.

ingrese la descripción de la imagen aquí

¿Por qué está permitido esto? Esta puede ser una pregunta estúpida e inútil, pero quiero que entiendas por qué te pregunto. Si se tratara de un caso secundario de una regla gramatical general, lo entendería: decidieron no hacer que la gramática general sea más difícil simplemente para no permitir una coma redundante al final de una lista de inicializadores. Pero no, la coma adicional está explícitamente permitida. Por ejemplo, no está permitido tener una coma redundante al final de una lista de argumentos de llamada a función (cuando la función toma ...), lo cual es normal .

Entonces, de nuevo, ¿hay alguna razón particular por la cual esta coma redundante esté explícitamente permitida?

Armen Tsirunyan
fuente
10
Todos parecen estar de acuerdo con la "facilidad de agregar una nueva línea", pero ¿las personas que definen las especificaciones del lenguaje realmente se preocupan por tales cosas? Si realmente son tan comprensivos, ¿por qué no ignoran una falta ;cuando está claro que el próximo token es en realidad una siguiente declaración?
YetAnotherUser
35
@YetAnotherUser: Sí, los diseñadores de idiomas consideran esas cosas. Permitirle colocar puntos y comas tendría un impacto mucho mayor y sería muy ambiguo en muchas partes del lenguaje (recuerde, el espacio en blanco no es semántico en C). Una coma extra es que este caso no es ambiguo. Un punto y coma extra casi nunca es ambiguo, por lo que también está permitido. En el caso de que sea ambiguo (después de un for()por ejemplo), al agregarlo, se genera una advertencia del compilador.
Rob Napier
55
@Tomalak: es ambiguo para un lector humano y, a menudo, es un error. Por eso arroja una advertencia. Del mismo modo, if (x = 1)no es ambiguo en la gramática, pero es muy ambiguo para los humanos, y por lo tanto arroja una advertencia.
Rob Napier
12
@Rob: Tu ifejemplo tampoco es ambiguo. ¡No creo que "ambiguo" signifique lo que crees que significa!
Carreras de ligereza en órbita
55
Siempre que estemos de acuerdo en que es algo útil para que el compilador nos proteja, mientras que una coma final en una declaración de matriz no es algo útil para que el compilador nos proteja.
Rob Napier

Respuestas:

436

Facilita la generación de código fuente y también la escritura de código que puede ampliarse fácilmente en una fecha posterior. Considere qué se requiere para agregar una entrada adicional a:

int a[] = {
   1,
   2,
   3
};

... debe agregar la coma a la línea existente y agregar una nueva línea. Compare eso con el caso donde los tres ya tienen una coma después, donde solo tiene que agregar una línea. Del mismo modo, si desea eliminar una línea, puede hacerlo sin preocuparse de si es la última línea o no, y puede reordenar las líneas sin jugar con comas. Básicamente significa que hay una uniformidad en la forma en que tratas las líneas.

Ahora piense en generar código. Algo como (pseudocódigo):

output("int a[] = {");
for (int i = 0; i < items.length; i++) {
    output("%s, ", items[i]);
}
output("};");

No debe preocuparse si el elemento actual que está escribiendo es el primero o el último. Mucho mas simple.

Jon Skeet
fuente
89
Además, cuando se utiliza un VCS, la "diferencia" entre dos versiones es más limpia, ya que solo una línea cambia cuando se agrega o elimina un elemento.
Kevin Panko
47
@ Néstor: ¿Por qué "desafortunado"? ¿Cuál es el inconveniente aquí? El hecho de que se haya tenido en cuenta la generación de código (y la fácil manipulación) para una pequeña parte del lenguaje no significa que deba ser la motivación principal detrás de todas las decisiones en el lenguaje. La inferencia de tipos, la eliminación de punto y coma, etc. tienen enormes implicaciones para el lenguaje. Estás configurando una falsa dicotomía aquí, OMI.
Jon Skeet
18
@ Néstor: ahí es donde gana el pragmatismo más de dogmatismo: ¿Por qué tiene que ser totalmente una cosa o totalmente la otra, cuando es más útil para ser una mezcla de ambos? ¿Cómo se interpone en el camino, pudiendo agregar una coma al final? ¿Es esta una inconsistencia que alguna vez te ha impedido en algún sentido? De lo contrario, sopese esa irrelevancia irrelevante con los beneficios prácticos de permitir una coma al final.
Jon Skeet
8
@Mrchief: no se trata de la tasa de escritura, se trata de simplicidad al copiar, eliminar o reordenar elementos. Me hizo la vida más simple ayer. Sin inconvenientes, ¿por qué no hacer la vida más fácil? En cuanto a tratar de señalar con el dedo a MS, sospecho firmemente que esto ha estado en C desde antes de que Microsoft existiera ... Usted dice que esta justificación parece extraña, pero apuesto a que beneficia a miles de desarrolladores en cientos de empresas todos los días. ¿No es una mejor explicación que buscar algo que beneficie a los escritores de compiladores?
Jon Skeet
66
Esto fue en K&R C.
Ferruccio
126

Es útil si haces algo como esto:

int a[] = {
  1,
  2,
  3, //You can delete this line and it's still valid
};
Drilldrick
fuente
66
JavaScript admite esta sintaxis: var a = [1, 2,];también la mayoría de los otros lenguajes que conozco ... ActionScript, Python, PHP.
Sean Fujiwara
14
@Sean Eso provocará un error de análisis en IE JavaScript, ¡así que ten cuidado!
Skilldrick
10
No es para mí en IE9. Pero hace algo extraño ... crea un elemento nulo. Tendré cuidado.
Sean Fujiwara
55
@Sean Lo sentimos, estás en lo correcto - no es un error de análisis en el IE, pero será insertar un conjunto de elementos extra para undefined.
Skilldrick
3
Lo más frustrante es que JSON no admite esta sintaxis.
Timmmm
38

Facilidad de uso para el desarrollador, creo.

int a[] = {
            1,
            2,
            2,
            2,
            2,
            2, /*line I could comment out easily without having to remove the previous comma*/
          }

Además, si por alguna razón tuvo una herramienta que generó código para usted; la herramienta no tiene que preocuparse por si es el último elemento en la inicialización o no.

vcsjones
fuente
32

Siempre he asumido que hace que sea más fácil agregar elementos adicionales:

int a[] = {
            5,
            6,
          };

simplemente se convierte en:

int a[] = { 
            5,
            6,
            7,
          };

en una fecha posterior.

Oliver Charlesworth
fuente
3
No creo que hacer que la edición sea un poco más rápida es una buena razón para estropear la sintaxis. En mi humilde opinión, esta es solo otra característica extraña de C ++.
Giorgio
3
@Giorgio: Bueno, se hereda de C. Es completamente posible que sea solo un descuido en la especificación del idioma original, que tiene un efecto secundario útil.
Oliver Charlesworth
Ok, no sabía que provenía de C. Acabo de comprobar que también está permitido en Java. Sin embargo, se siente un poco extraño: en mi intuición, la coma es un separador, no un terminador. Además, es posible omitir la última coma. Entonces, ¿es un terminador, un separador o ambos? Pero bueno, esta función está disponible y es bueno saberlo.
Giorgio
11
@Giorgio: el código fuente es para humanos, no para máquinas. Pequeñas cosas como esta para evitar que cometamos errores simples de transposición son una bendición, no un descuido. Como referencia, también funciona de esta manera en PHP y ECMAScript (y, por lo tanto, JavaScript y ActionScript), aunque no es válido en la notación de objetos JavaScript (JSON) (por ejemplo, [1,2,3,]está bien, pero {a:1, b:2, c:3,}no lo está).
Publicado el
1
@Groky: Cuanto más lo pienso, más me convenzo de que la sintaxis de un lenguaje de programación debe ser lo más simple y consistente posible y con la menor cantidad de excepciones posible: esto hace que sea más fácil aprender el idioma (menos reglas para recordar ) La ventaja de guardar una o dos pulsaciones de teclas al agregar / eliminar un elemento a / de una lista (que, por cierto, no hago a menudo en comparación con la cantidad total de tiempo que paso codificando) me parece bastante trivial en comparación con teniendo una sintaxis claramente definida.
Giorgio
21

Todo lo que todos dicen sobre la facilidad de agregar / eliminar / generar líneas es correcto, pero el verdadero lugar en el que brilla esta sintaxis es cuando se combinan los archivos de origen. Imagina que tienes esta matriz:

int ints[] = {
    3,
    9
};

Y suponga que ha verificado este código en un repositorio.

Luego, tu amigo lo edita y agrega al final:

int ints[] = {
    3,
    9,
    12
};

Y simultáneamente lo editas, agregando al principio:

int ints[] = {
    1,
    3,
    9
};

Semánticamente, este tipo de operaciones (agregar al principio, agregar al final) debe ser completamente seguro y su software de versiones (con suerte git) debería ser capaz de automatizar. Lamentablemente, este no es el caso porque su versión no tiene coma después del 9 y la de su amigo sí. Mientras que, si la versión original tuviera el 9 final, se habrían automatizado.

Entonces, mi regla general es: use la coma final si la lista abarca varias líneas, no la use si la lista está en una sola línea.

amoss
fuente
15

La coma final, creo, está permitida por razones de compatibilidad con versiones anteriores. Hay una gran cantidad de código existente, principalmente generado automáticamente, que pone una coma final. Hace que sea más fácil escribir un bucle sin condiciones especiales al final. p.ej

for_each(my_inits.begin(), my_inits.end(),
[](const std::string& value) { std::cout << value << ",\n"; });

Realmente no hay ninguna ventaja para el programador.

PD: Aunque es más fácil generar automáticamente el código de esta manera, en realidad siempre me ocupé de no poner la coma final, los esfuerzos son mínimos, se mejora la legibilidad y eso es más importante. Escribe el código una vez, lo lee muchas veces.

Gene Bushuyev
fuente
55
No estoy de acuerdo por completo; [Es mi opinión que] ha encontrado su camino en muchos lenguajes creados mucho después de C, precisamente porque es ventajoso para el programador poder cambiar el contenido de la matriz, comentar líneas de todas formas, etc. sin tener que preocuparse por tontos errores de sintaxis inducidos por transposición. ¿Ya no estamos lo suficientemente estresados?
Publicado el
12
@Dereleased: por la misma lógica, ¿por qué no debería permitirse el arrastre (nada), qué tal int a = b + c +;o if(a && b &&);será más fácil copiar y pegar cualquier cosa al final y más fácil escribir generadores de código. Este problema es trivial y subjetivo, en tales casos siempre es bueno hacer lo mejor para el lector de código.
Gene Bushuyev
1
@Gene Bushuyev: ¡Exactamente! A menudo tengo expresiones largas con + o &&, con el operador al final de la línea y, por supuesto, tengo que pasar un tiempo extra cuando quiero eliminar el último operando de la expresión. ¡Creo que esta sintaxis de coma es realmente extraña!
Giorgio
2
@GeneBushuyev - No estoy de acuerdo con eso. Si bien permitir el uso de comas finales en matrices y similares es una función de eliminación de errores y hace que su vida sea más fácil como programador, por el simple hecho de facilitar la lectura, tomaría medidas para eliminar las declaraciones AND (&&) finales, plusses y otros operadores diversos de los condicionales declaraciones. Es simplemente feo, en mi opinión.
Sune Rasmussen
2
Con respecto al &&operador, a veces hago condicionales como if (true \n && b1 \n && b2)para poder agregar y eliminar líneas según sea necesario.
Christian Mann
12

Una de las razones por las que esto está permitido hasta donde yo sé es que debería ser simple generar código automáticamente; no necesita ningún manejo especial para el último elemento.

Fredrik Pihl
fuente
11

Hace que los generadores de código que escupen matrices o enumeraciones sean más fáciles.

Imagina:

std::cout << "enum Items {\n";
for(Items::iterator i(items.begin()), j(items.end); i != j; ++i)
    std::cout << *i << ",\n";
std::cout << "};\n";

Es decir, no es necesario hacer un manejo especial del primer o último elemento para evitar escupir la coma final.

Si el generador de código está escrito en Python, por ejemplo, es fácil evitar escupir la coma final utilizando la str.join()función:

print("enum Items {")
print(",\n".join(items))
print("}")
Maxim Egorushkin
fuente
10

Me sorprende que, después de todo este tiempo, nadie haya citado el Manual de referencia anotado de C ++ ( ARM ), que dice lo siguiente sobre [dcl.init] con énfasis en el mío:

Claramente, hay demasiadas anotaciones para las inicializaciones, pero cada una parece servir bien a un estilo particular de uso. La notación = {initializer_list, opt} fue heredada de C y sirve bien para la inicialización de estructuras de datos y matrices. [...]

aunque la gramática ha evolucionado desde que se escribió ARM, el origen permanece.

y podemos ir a la justificación de C99 para ver por qué esto se permitió en C y dice:

K&R permite una coma final en un inicializador al final de una lista de inicializadores. El Estándar ha conservado esta sintaxis, ya que proporciona flexibilidad para agregar o eliminar miembros de una lista de inicializadores, y simplifica la generación automática de tales listas.

Shafik Yaghmour
fuente
1
Vota por la respuesta más respaldada por la literatura y la verdadera fuente de esta característica.
Marko
10

Veo un caso de uso que no se mencionó en otras respuestas, nuestras Macros favoritas:

int a [] = {
#ifdef A
    1, //this can be last if B and C is undefined
#endif
#ifdef B
    2,
#endif
#ifdef C
    3,
#endif
};

Agregar macros para manejar el último ,sería un gran dolor. Con este pequeño cambio en la sintaxis, esto es trivial de administrar. Y esto es más importante que el código generado por la máquina porque generalmente es mucho más fácil hacerlo en una lengua completa de Turing que un preprocesador muy limitado.

Yankees
fuente
7

El único lenguaje donde, en la práctica *, no está permitido es Javascript, y causa una cantidad innumerable de problemas. Por ejemplo, si copia y pega una línea desde el centro de la matriz, péguela al final y olvidó eliminar la coma, entonces su sitio estará totalmente roto para sus visitantes de IE.

* En teoría está permitido, pero Internet Explorer no sigue el estándar y lo trata como un error

Thomas Bonini
fuente
Las "matrices" de JavaScript (que son solo objetos con una propiedad de longitud mágica) son bastante inusuales de todos modos: var x = [,,,]es legal (excepto en IE <9, pero la especificación dice que es legal)
Peter C
De acuerdo con la especificación ECMAScript, es perfectamente válido; en teoría debería funcionar en cualquier navegador que implemente JavaScript de acuerdo con dicha especificación, particularmente la parte de la especificación que se encuentra aquí .
Publicado el
1
Desafortunadamente, JavaScript se trata de crear aplicaciones para el público. Entonces, no, no es perfectamente válido cuando ~ 50% de los usuarios tendrían problemas para usar su aplicación. Y sí, si pudiera, prohibiría IE <9 - pasarían demasiadas horas haciendo un buen código trabajando allí ...
kgadek
@Dere: sí, lo dije en mi respuesta =)
Thomas Bonini
@Dereleased microsoft inventa sus propias especificaciones y comandos para que otros cumplan al menos esa mentalidad está cambiando (gracias a Dios)
Chris McGrath
7

Es más fácil para las máquinas, es decir, el análisis y la generación de código. También es más fácil para los humanos, es decir, modificación, comentarios y elegancia visual a través de la coherencia.

Suponiendo C, ¿escribirías lo siguiente?

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    puts("Line 1");
    puts("Line 2");
    puts("Line 3");

    return EXIT_SUCCESS
}

No. No solo porque la declaración final es un error, sino también porque es inconsistente. Entonces, ¿por qué hacer lo mismo con las colecciones? Incluso en los idiomas que le permiten omitir los últimos puntos y comas y comas, a la comunidad generalmente no le gusta. A la comunidad de Perl, por ejemplo, no parece gustarle omitir punto y coma, excluir frases. Aplican eso a las comas también.

No omita comas en colecciones multilínea por la misma razón por la que no omite puntos y comas para bloques de código multilínea. Quiero decir, no lo harías incluso si el idioma lo permitiera, ¿verdad? ¿Correcto?

Louis
fuente
Hay idiomas (por ejemplo, Pascal) que lo permiten. Es decir, tienes que elegir entre; como terminador (C) o como separador (Pascal). Igual por ','. Estaría bien para mí si ',' es un terminador, pero entonces {1, 2, 3} debe ser un error de sintaxis.
Giorgio
6

La razón es trivial: facilidad para agregar / eliminar líneas.

Imagine el siguiente código:

int a[] = {
   1,
   2,
   //3, // - not needed any more
};

Ahora, puede agregar / eliminar elementos fácilmente a la lista sin tener que agregar / eliminar la coma final a veces.

A diferencia de otras respuestas, realmente no creo que la facilidad de generar la lista sea una razón válida: después de todo, es trivial que el código ponga en un caso especial la última (o primera) línea. Los generadores de código se escriben una vez y se usan muchas veces.

Vlad
fuente
6

Permite que cada línea siga la misma forma. En primer lugar, esto hace que sea más fácil agregar nuevas filas y que un sistema de control de versiones rastree el cambio de manera significativa y también le permite analizar el código más fácilmente. No puedo pensar en una razón técnica.

Mark B
fuente
5

Esto está permitido para proteger de los errores causados ​​por mover elementos en una larga lista.

Por ejemplo, supongamos que tenemos un código similar a este.

#include <iostream>
#include <string>
#include <cstddef>
#define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array))
int main() {
    std::string messages[] = {
        "Stack Overflow",
        "Super User",
        "Server Fault"
    };
    size_t i;
    for (i = 0; i < ARRAY_SIZE(messages); i++) {
        std::cout << messages[i] << std::endl;
    }
}

Y es genial, ya que muestra la trilogía original de los sitios de Stack Exchange.

Stack Overflow
Super User
Server Fault

Pero hay un problema con eso. Verá, el pie de página en este sitio web muestra Falla del servidor antes del Superusuario. Mejor arreglar eso antes de que alguien se dé cuenta.

#include <iostream>
#include <string>
#include <cstddef>
#define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array))
int main() {
    std::string messages[] = {
        "Stack Overflow",
        "Server Fault"
        "Super User",
    };
    size_t i;
    for (i = 0; i < ARRAY_SIZE(messages); i++) {
        std::cout << messages[i] << std::endl;
    }
}

Después de todo, mover las líneas no podría ser tan difícil, ¿verdad?

Stack Overflow
Server FaultSuper User

Lo sé, no hay un sitio web llamado "Server FaultSuper User", pero nuestro compilador afirma que existe. Ahora, el problema es que C tiene una función de concatenación de cadenas, que le permite escribir dos cadenas entre comillas dobles y concatenarlas sin usar nada (un problema similar también puede ocurrir con los enteros, ya que el -signo tiene múltiples significados).

¿Y si la matriz original tuviera una coma inútil al final? Bueno, las líneas se moverían, pero ese error no habría sucedido. Es fácil perderse algo tan pequeño como una coma. Si recuerda poner una coma después de cada elemento de la matriz, tal error simplemente no puede suceder. No querrá perder cuatro horas depurando algo, hasta que encuentre que la coma es la causa de sus problemas .

Konrad Borowski
fuente
4

Como muchas cosas, la coma final en un inicializador de matriz es una de las cosas que C ++ heredó de C (y tendrá que soportar para siempre). Una vista totalmente diferente de las que se encuentran aquí se menciona en el libro "Deep C secrets" .

Luego de un ejemplo con más de una "paradoja de coma":

char *available_resources[] = {
"color monitor"           ,
"big disk"                ,
"Cray"                      /* whoa! no comma! */
"on-line drawing routines",
"mouse"                   ,
"keyboard"                ,
"power cables"            , /* and what's this extra comma? */
};

leemos :

... que coma final después de que el inicializador final no es un error tipográfico, pero un bache en la sintaxis prorrogados de C aborigen . Su presencia o ausencia está permitida pero no tiene importancia . La justificación alegada en la justificación de ANSI C es que facilita la generación automatizada de C. El reclamo sería más creíble si se permitieran las comas finales en cada lista separada por comas , como en las declaraciones de enumeración o en los declaradores de variables múltiples en una sola declaración. Ellos no son.

... para mí esto tiene más sentido

Nikos Athanasiou
fuente
2
La prohibición contra la coma en el enumcaso es algo interesante, ya que ese es el caso en el que la coma faltante plantearía la menor ambigüedad. Dado struct foo arr[] = {{1,2,3,4,5}, {3,4,5,6,7}, }; Hay dos significados sensibles que el lenguaje podría asignar: crear una matriz de dos elementos o crear una matriz de tres elementos donde el último elemento tenga valores predeterminados. Si C hubiera adoptado la interpretación posterior, podría ver prohibido enum foo {moe, larry, curly, };en el principio de que solo debería haber una forma de escribir la declaración (sin la coma), pero ...
supercat
1
... dado que C está dispuesto a ignorar la coma en un caso en el que razonablemente se le pudo haber asignado (pero no se le asignó) un significado significativo (lo que sería un fuerte argumento a favor de prohibirlo allí) es curioso que no No está dispuesto en un caso en el que la coma no puede tener un significado [incluso si se interpreta enum foo {moe,,larry,curly,};que se omite un número entre moey larry, generalmente no importaría si la coma final se procesó o se ignoró. El único caso en el que podría importar sería si el último elemento fuera el valor máximo para su tipo declarado, y eso ...
supercat
1
... podría manejarse simplemente diciendo que el desbordamiento que ocurre después del último valor de enumeración asignado debe ignorarse.
supercat
@supercat Hay lenguajes, como C #, en los que la investigación de diseño a priori llega a considerar las características de IDE y la integración al devolver el lenguaje. C no era (y no pudo haber sido) uno de estos lenguajes.
Nikos Athanasiou
Incluso con lenguajes como C #, los objetivos de diseño cambiantes han resultado en inconsistencias de diseño bastante severas. Por ejemplo, el lenguaje se abstuvo de admitir cualquier forma de sobrecarga de tipo de retorno para los métodos y operadores normales (a pesar de que el marco subyacente podría admitirlo) porque se consideraba contrario al objetivo de tener un lenguaje simple de compilar, pero La evaluación lambda incluye reglas de inferencia de tipo cuya resolución es NP-completa. Añadiendo nuevo método / sobrecarga de operadores reglas podrían romper el código existente (aunque creo que las buenas reglas podrían minimizar este peligro) ...
supercat
2

Además de la generación de código y la facilidad de edición, si desea implementar un analizador sintáctico, este tipo de gramática es más simple y fácil de implementar. C # sigue esta regla en varios lugares en los que hay una lista de elementos separados por comas, como los elementos en una enumdefinición.

Iravanchi
fuente
1

Facilita la generación de código, ya que solo necesita agregar una línea y no necesita tratar de agregar la última entrada como si fuera un caso especial. Esto es especialmente cierto cuando se usan macros para generar código. Hay un impulso para tratar de eliminar la necesidad de macros del lenguaje, pero gran parte del lenguaje evolucionó de la mano con las macros disponibles. La coma adicional permite definir y usar macros como las siguientes:

#define LIST_BEGIN int a[] = {
#define LIST_ENTRY(x) x,
#define LIST_END };

Uso:

LIST_BEGIN
   LIST_ENTRY(1)
   LIST_ENTRY(2)
LIST_END

Es un ejemplo muy simplificado, pero a menudo las macros usan este patrón para definir cosas como mapas y tablas de despacho, mensaje, evento o traducción. Si no se permitía una coma al final, necesitaríamos un especial:

#define LIST_LAST_ENTRY(x) x

y eso sería muy incómodo de usar.

Scott Langham
fuente
0

De modo que cuando dos personas agregan un nuevo elemento en una lista en ramas separadas, Git puede combinar los cambios correctamente, ya que Git funciona de forma lineal.

noɥʇʎԀʎzɐɹƆ
fuente
-4

Si usa una matriz sin una longitud especificada, VC ++ 6.0 puede identificar automáticamente su longitud, por lo que si usa "int a [] = {1,2,};" la longitud de a es 3, pero la última no ' t inicializado, puedes usar "cout <

zhi_jian
fuente
¿Es esto un error para VC6 que no cumple con el estándar?
Thomson