int a [] = {1,2,}; Vírgula estranha permitida. Alguma razão em particular?

334

Talvez eu não seja deste planeta, mas parece-me que o seguinte deve ser um erro de sintaxe:

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

Mas isso não. Fiquei surpreso quando esse código foi compilado no Visual Studio, mas aprendi a não confiar no compilador MSVC no que diz respeito às regras do C ++, por isso verifiquei o padrão e ele também é permitido pelo padrão. Você pode ver 8.5.1 para as regras gramaticais se não acredita em mim.

insira a descrição da imagem aqui

Por que isso é permitido? Esta pode ser uma pergunta inútil estúpida, mas quero que você entenda por que estou perguntando. Se fosse um sub-caso de uma regra gramatical geral, eu entenderia - eles decidiram não tornar a gramática geral mais difícil apenas para impedir uma vírgula redundante no final de uma lista de inicializadores. Mas não, a vírgula adicional é explicitamente permitida. Por exemplo, não é permitido ter uma vírgula redundante no final de uma lista de argumentos de chamada de função (quando a função aceitar ...), o que é normal .

Então, novamente, existe algum motivo específico para que essa vírgula redundante seja explicitamente permitida?

Armen Tsirunyan
fonte
10
Todos parecem concordar com a "facilidade de adicionar nova linha" - mas as pessoas que definem as especificações de linguagem realmente se preocupam com essas coisas? Se eles realmente entendem isso, por que não ignoram uma falta ;quando fica claro o próximo token é na verdade uma próxima declaração.
YetAnotherUser
35
@YetAnotherUser: Sim, os designers de idiomas consideram essas coisas. Permitir que você elimine ponto e vírgula teria um impacto muito maior e seria altamente ambíguo em muitas partes da linguagem (lembre-se, o espaço em branco não é semântico em C). Uma vírgula extra neste caso não é ambígua. Um ponto e vírgula extra quase nunca é ambíguo, e também é permitido. No caso em que é ambíguo (depois de um for()por exemplo), a adição dele lança um aviso do compilador.
Rob Napier
5
@Tomalak: É ambíguo para um leitor humano e geralmente é um erro. É por isso que lança um aviso. Da mesma forma, if (x = 1)a gramática não é ambígua, mas é muito ambígua para os seres humanos e, portanto, lança um aviso.
Rob Napier
12
@ Rob: Seu ifexemplo também não é ambíguo. Eu não acho que "ambíguo" significa o que você pensa que significa!
Lightness Races em órbita
5
Desde que concordemos que é algo útil para o compilador nos proteger, enquanto uma vírgula à direita em uma declaração de matriz não é algo útil para o compilador nos proteger.
Rob Napier

Respostas:

435

Isso facilita a geração de código-fonte e também a gravação de código que pode ser facilmente estendido posteriormente. Considere o que é necessário para adicionar uma entrada extra a:

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

... você precisa adicionar a vírgula à linha existente e adicionar uma nova linha. Compare isso com o caso em que os três tenham uma vírgula depois, onde você apenas precisará adicionar uma linha. Da mesma forma, se você deseja remover uma linha, pode fazê-lo sem se preocupar se é a última ou não, e pode reordenar as linhas sem mexer em vírgulas. Basicamente, significa que há uma uniformidade na forma como você trata as linhas.

Agora pense em gerar código. Algo como (pseudo-código):

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

Não precisa se preocupar se o item atual que você está escrevendo é o primeiro ou o último. Muito mais simples.

Jon Skeet
fonte
89
Além disso, ao usar um VCS, o "diff" entre duas versões é mais limpo, pois apenas uma linha muda quando um item é adicionado ou removido.
Kevin Panko
47
@ Néstor: Por que "infeliz"? Qual é a desvantagem aqui? Só porque alguma consideração foi dada à geração de código (e fácil manipulação) para uma pequena parte da linguagem não significa que ela tenha que ser a principal motivação por trás de todas as decisões na linguagem. Inferência de tipo, remoção de ponto-e-vírgula etc. têm enormes implicações para o idioma. Você está montando uma falsa dicotomia aqui, IMO.
21911 Jon Skeet
18
@ Néstor: é aí que vitórias pragmatismo sobre o dogmatismo: Por que tem que ser totalmente uma coisa ou totalmente o outro, quando é mais útil para ser uma mistura de ambos? Como isso realmente atrapalha, sendo possível adicionar uma vírgula no final? Isso é uma inconsistência que já o impediu em algum sentido? Caso contrário, avalie essa deselegância irrelevante contra os benefícios práticos de permitir uma vírgula no final.
22811 Jon Skeet
8
@Mchchief: Não se trata de digitar a taxa - é uma questão de simplicidade ao copiar, remover ou reordenar itens. Isso tornou minha vida mais simples ontem. Sem desvantagem, por que não facilitar a vida? Quanto a tentar apontar o dedo para a MS, suspeito fortemente que isso esteja em C desde antes da existência da Microsoft ... Você diz que essa justificativa parece estranha, mas aposto que beneficia milhares de desenvolvedores em centenas de empresas todos os dias. Essa não é uma explicação melhor do que procurar algo que beneficie os escritores de compiladores?
Jon Skeet
6
Isso foi em K&R C.
Ferruccio
126

É útil se você fizer algo assim:

int a[] = {
  1,
  2,
  3, //You can delete this line and it's still valid
};
Skilldrick
fonte
6
O JavaScript suporta esta sintaxe:, var a = [1, 2,];assim como a maioria das outras linguagens que conheço ... ActionScript, Python, PHP.
Sean Fujiwara
14
@Sean Isso causará um erro de análise no IE JavaScript, então cuidado!
Skilldrick 15/08/11
10
Não é para mim no IE9. Mas faz algo estranho ... cria um elemento nulo. Eu vou tomar cuidado.
Sean Fujiwara
5
@Sean Desculpe, você está correto - não é um erro de interpretação no IE, mas irá inserir um elemento set extra para undefined.
Skilldrick
3
O mais frustrante é que o JSON não suporta essa sintaxe.
Timmmm 19/01/19
38

Facilidade de uso para o desenvolvedor, eu acho.

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

Além disso, se por algum motivo você tiver uma ferramenta que gerou código para você; a ferramenta não precisa se preocupar se é o último item na inicialização ou não.

vcsjones
fonte
32

Sempre presumi que seja mais fácil acrescentar elementos extras:

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

simplesmente se torna:

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

Numa data posterior.

Oliver Charlesworth
fonte
3
Não acho que tornar a edição um pouco mais rápida seja uma boa razão para atrapalhar a sintaxe. IMHO este é apenas mais um recurso estranho de C ++.
Giorgio
3
@Giorgio: Bem, é herdado de C. É perfeitamente possível que seja apenas uma supervisão na especificação do idioma original, que tenha um efeito colateral útil.
Oliver Charlesworth
Ok, eu não sabia que era do C. Acabei de verificar se é permitido em Java também. Parece meio estranho: na minha intuição, a vírgula é um separador, não um terminador. Além disso, é possível omitir a última vírgula. Então, é um terminador, um separador ou ambos? Mas tudo bem, esse recurso está disponível e é bom saber.
Giorgio
11
@Giorgio - o código fonte é para humanos, não para máquinas. Pequenas coisas como essa para nos impedir de cometer erros de transposição simples são uma bênção, não uma supervisão. Para referência, ele também funciona dessa maneira em PHP e ECMAScript (e, portanto, JavaScript e ActionScript), embora seja inválido na notação de objeto JavaScript (JSON) (por exemplo, [1,2,3,]está OK, mas {a:1, b:2, c:3,}não está).
Cancelado em 12/08/11
11
@Groky: Quanto mais eu penso sobre isso, mais estou convencido de que a sintaxe de uma linguagem de programação deve ser a mais simples e consistente possível e com o mínimo de exceções possível: isso facilita o aprendizado da linguagem (menos regras a serem lembradas) ) A vantagem de salvar um ou dois pressionamentos de tecla ao adicionar / remover um item de / para uma lista (que, a propósito, eu não faço isso com frequência em comparação com a quantidade total de tempo que passo codificando) me parece bastante trivial em comparação com tendo uma sintaxe claramente definida.
Giorgio
21

Tudo o que todos estão dizendo sobre a facilidade de adicionar / remover / gerar linhas está correto, mas o lugar real em que essa sintaxe brilha é ao mesclar arquivos de origem. Imagine que você tem essa matriz:

int ints[] = {
    3,
    9
};

E suponha que você tenha verificado esse código em um repositório.

Em seguida, seu amigo o edita, adicionando ao final:

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

E você o edita simultaneamente, adicionando ao início:

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

Semanticamente, esses tipos de operações (adicionando ao início e adicionando ao final) devem ser totalmente mesclados e o software de controle de versão (espero que o git) seja capaz de emergir automaticamente. Infelizmente, esse não é o caso, porque sua versão não tem vírgula após o 9 e o do seu amigo. Considerando que, se a versão original tivesse o 9 à direita, eles teriam se alimentado.

Portanto, minha regra geral é: use a vírgula à direita se a lista abranger várias linhas, não a use se a lista estiver em uma única linha.

amoss
fonte
15

Vírgula à direita, acredito que seja permitida por motivos de compatibilidade com versões anteriores. Há muito código existente, principalmente gerado automaticamente, o que coloca uma vírgula à direita. Facilita a gravação de um loop sem condição especial no final. por exemplo

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

Não há realmente nenhuma vantagem para o programador.

PS Embora seja mais fácil gerar automaticamente o código dessa maneira, eu sempre tomei o cuidado de não colocar a vírgula à direita, os esforços são mínimos, a legibilidade é aprimorada e isso é mais importante. Você escreve o código uma vez e o lê muitas vezes.

Gene Bushuyev
fonte
5
Eu discordo completamente; [É minha opinião] que ele chegou a muitas linguagens criadas muito depois do C, precisamente porque é vantajoso para o programador poder mudar o conteúdo da matriz, comentar as linhas à vontade e assim por diante, sem ter que se preocupar com erros de sintaxe induzidos por transposição. Ainda não estamos estressados ​​o suficiente?
Cancelado em 12/08/11
12
@Dereleased - pela mesma lógica, por que não deve ser permitido rastrear (nada), que tal int a = b + c +;ou if(a && b &&);será mais fácil copiar e colar qualquer coisa no final e mais fácil escrever geradores de código. Esse problema é trivial e subjetivo; nesses casos, é sempre bom fazer o que é melhor para o leitor de código.
Gene Bushuyev 12/08
11
@Gene Bushuyev: Exatamente! Costumo ter expressões longas com + ou &&, com o operador no final da linha e, é claro, preciso dedicar mais tempo para remover o último operando da expressão. Eu acho que essa sintaxe de vírgula é realmente estranha!
Giorgio
2
@GeneBushuyev - Eu discordo disso. Embora permitir vírgulas finais em matrizes e similares seja um recurso de remoção de bugs e torne sua vida mais fácil como programador, eu, por pura questão de legibilidade, adotaria medidas para remover instruções E (&&), pontos positivos e outros operadores diversos de condicionais afirmações. É simplesmente feio, IMO.
Sune Rasmussen
2
Em relação ao &&operador, às vezes eu faço condicionais como if (true \n && b1 \n && b2)para poder adicionar e remover linhas conforme necessário.
Christian Mann
12

Uma das razões pelas quais isso é permitido, até onde eu sei, é que deve ser simples gerar código automaticamente; você não precisa de nenhum tratamento especial para o último elemento.

Fredrik Pihl
fonte
11

Facilita os geradores de código que distribuem matrizes ou enumerações.

Imagine:

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

Ou seja, não é necessário fazer tratamento especial do primeiro ou do último item para evitar cuspir a vírgula à direita.

Se o gerador de código for escrito em Python, por exemplo, é fácil evitar cuspir a vírgula final usando a str.join()função:

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

Estou surpreso depois de todo esse tempo que ninguém citou o Annotated C ++ Reference Manual ( ARM ), ele diz o seguinte sobre [dcl.init] com ênfase na minha:

Claramente, existem muitas notações para inicializações, mas cada uma parece servir bem a um estilo de uso específico. A notação = {initializer_list, opt} foi herdada de C e serve bem para a inicialização de estruturas e matrizes de dados. [...]

embora a gramática tenha evoluído desde ARM foi escrito, a origem permanece.

e podemos ir para a lógica C99 para ver por que isso foi permitido em C e ele diz:

K&R permite uma vírgula à direita em um inicializador no final de uma lista de inicializadores. O Padrão manteve essa sintaxe, pois fornece flexibilidade na adição ou exclusão de membros de uma lista de inicializadores e simplifica a geração de máquinas dessas listas.

Shafik Yaghmour
fonte
11
Voto positivo para a resposta mais copiada da literatura e a verdadeira fonte desse recurso.
Marko
10

Vejo um caso de uso que não foi mencionado em outras respostas, nossas 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
};

Adicionar macros para lidar com o último ,seria uma grande dor. Com essa pequena alteração na sintaxe, isso é trivial de gerenciar. E isso é mais importante que o código gerado por máquina, porque geralmente é muito mais fácil fazê-lo na linguagem completa de Turing do que no pré-processador muito limitado.

Yankes
fonte
7

O único idioma em que - na prática * - não é permitido é o Javascript e causa uma quantidade incontável de problemas. Por exemplo, se você copiar e colar uma linha do meio da matriz, cole-a no final e se esqueça de remover a vírgula, seu site ficará totalmente quebrado para os visitantes do IE.

* Em teoria, é permitido, mas o Internet Explorer não segue o padrão e o trata como um erro

Thomas Bonini
fonte
"Matrizes" de JavaScript (que são apenas objetos com uma propriedade de comprimento mágico) são de qualquer maneira bastante incomum: var x = [,,,]é legal (exceto no IE <9, mas a especificação diz que é legal)
Peter C
De acordo com a especificação ECMAScript, é perfeitamente válido; em teoria, ele deve funcionar em qualquer navegador que implemente JavaScript de acordo com a referida especificação, particularmente a parte da especificação encontrada aqui .
Cancelado em 12/08/11
11
Infelizmente, o JavaScript consiste em criar aplicativos para o público. Portanto, não, não é perfeitamente válido quando ~ 50% dos usuários têm problemas ao usar seu aplicativo. E sim, se eu pudesse eu iria proibir IE <9 - demais horas gastar em apenas fazendo um bom código trabalhando lá ...
kgadek
@Dere: Sim, eu disse isso na minha resposta =)
Thomas Bonini
@Dereleased inventa microsoft suas próprias especificações e comandos que outra pousar em menos essa mentalidade está mudando (graças a Deus)
Chris McGrath
7

É mais fácil para máquinas, ou seja, análise e geração de código. Também é mais fácil para os seres humanos, ou seja, modificação, comentários e elegância visual através da consistência.

Supondo C, você escreveria o seguinte?

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

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

    return EXIT_SUCCESS
}

Não. Não apenas porque a declaração final é um erro, mas também porque é inconsistente. Então, por que fazer o mesmo com as coleções? Mesmo em idiomas que permitem omitir os últimos pontos e vírgulas e vírgulas, a comunidade geralmente não gosta. A comunidade Perl, por exemplo, não parece omitir ponto-e-vírgula, excluindo linhas. Eles aplicam isso a vírgulas também.

Não omita vírgulas em coleções com várias linhas pelo mesmo motivo que não omite ponto e vírgula para blocos de código com várias linhas. Quero dizer, você não faria isso mesmo que a linguagem permitisse, certo? Direita?

Louis
fonte
Existem idiomas (por exemplo, Pascal) que permitem isso. Ou seja, você tem que escolher entre; como terminador (C) ou como separador (Pascal). O mesmo para ','. Seria bom para mim se ',' for um terminador, mas {1, 2, 3} deve ser um erro de sintaxe.
Giorgio
6

O motivo é trivial: facilidade de adicionar / remover linhas.

Imagine o seguinte código:

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

Agora, você pode adicionar / remover itens facilmente à lista sem precisar adicionar / remover a vírgula à direita algumas vezes.

Ao contrário de outras respostas, eu realmente não acho que a facilidade de gerar a lista seja uma razão válida: afinal, é trivial que o código tenha um caso especial na última (ou primeira) linha. Os geradores de código são escritos uma vez e usados ​​muitas vezes.

Vlad
fonte
6

Ele permite que todas as linhas sigam a mesma forma. Primeiro, isso facilita a adição de novas linhas e um sistema de controle de versão rastreia a alteração de forma significativa, além de permitir a análise do código com mais facilidade. Não consigo pensar em uma razão técnica.

Mark B
fonte
5

Isso permite proteger contra erros causados ​​pela movimentação de elementos em uma longa lista.

Por exemplo, vamos assumir que temos um código parecido com 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;
    }
}

E é ótimo, pois mostra a trilogia original dos sites do Stack Exchange.

Stack Overflow
Super User
Server Fault

Mas há um problema com isso. Veja, o rodapé deste site mostra a falha do servidor antes do superusuário. Melhor consertar isso antes que alguém perceba.

#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;
    }
}

Afinal, mover linhas ao redor não poderia ser tão difícil, poderia ser?

Stack Overflow
Server FaultSuper User

Eu sei, não existe um site chamado "Server FaultSuper User", mas nosso compilador afirma que ele existe. Agora, o problema é que C possui um recurso de concatenação de strings, que permite escrever duas strings com aspas duplas e concatená-las usando nada (um problema semelhante também pode acontecer com números inteiros, como- sinal tem vários significados).

Agora, e se a matriz original tivesse uma vírgula inútil no final? Bem, as linhas seriam deslocadas, mas esse erro não teria acontecido. É fácil perder algo tão pequeno quanto uma vírgula. Se você se lembrar de colocar uma vírgula após cada elemento da matriz, esse erro simplesmente não poderá ocorrer. Você não gostaria de perder quatro horas depurando alguma coisa, até descobrir que a vírgula é a causa dos seus problemas .

Konrad Borowski
fonte
4

Como muitas coisas, a vírgula à direita em um inicializador de matriz é uma das coisas que o C ++ herdou do C (e terá que suportar para sempre). Uma visão totalmente diferente das colocadas aqui é mencionada no livro "Deep C secrets" .

Nele, após um exemplo com mais de um "paradoxo de vírgula":

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? */
};

nós lemos :

... aquela vírgula final após o inicializador final não é um erro de digitação, mas um sinal na sintaxe herdada do C aborígene . Sua presença ou ausência é permitida, mas não tem significado . A justificativa reivindicada na lógica ANSI C é que facilita a geração automatizada de C. A reivindicação seria mais credível se vírgulas à direita fossem permitidas em todas as listas classificadas por vírgulas , como nas declarações enum ou em vários declaradores variáveis ​​em uma única declaração. Eles não são.

... para mim isso faz mais sentido

Nikos Athanasiou
fonte
2
A proibição contra a vírgula no enumcaso é um tanto interessante, pois é o caso em que a vírgula ausente representaria a menor ambiguidade. Dado struct foo arr[] = {{1,2,3,4,5}, {3,4,5,6,7}, }; existem dois significados sensatos que o idioma pode atribuir: criar uma matriz de dois elementos ou criar uma matriz de três elementos em que o último item tenha valores padrão. Se C tivesse adotado a interpretação posterior, eu pude ver proibindo enum foo {moe, larry, curly, };o princípio de que deveria haver apenas uma maneira de escrever a declaração (sem a vírgula), mas ... #
11335
11
... dado que C está disposto a ignorar a vírgula em um caso em que poderia razoavelmente ter recebido (mas não ter) um significado significativo (o que seria um forte argumento a favor de proibi-la), é curioso que não seja. não está disposto a um caso em que a vírgula não possa ter um significado [mesmo que alguém interprete enum foo {moe,,larry,curly,};como pular um número entre moee larry, geralmente não importa se a vírgula à direita foi processada ou ignorada. O único caso em que isso poderia importar seria se o último item fosse o valor máximo para o seu tipo declarado, e que ... #
308
11
... poderia ser tratado simplesmente dizendo que o excesso que ocorre após o último valor de enumeração atribuído deve ser ignorado.
Supercat
@supercat Existem idiomas, como o C #, nos quais a pesquisa de design a priori chega a considerar os recursos e a integração do IDE ao desenvolver a linguagem. C não era (e não poderia ter sido) um desses idiomas.
Nikos Athanasiou
Mesmo em linguagens como C #, a alteração dos objetivos do projeto resultou em algumas inconsistências bastante graves. Por exemplo, a linguagem se absteve de oferecer suporte a qualquer forma de sobrecarga do tipo retorno para métodos e operadores normais (mesmo que a estrutura subjacente pudesse suportá-la) porque era vista como contrária ao objetivo de ter uma linguagem simples de compilar, mas A avaliação lambda inclui regras de inferência de tipo cuja resolução é NP-completa. Adicionar novas regras de sobrecarga de método / operador pode violar o código existente (embora eu ache que boas regras possam minimizar esse perigo) ... #
226
2

Além da facilidade de geração e edição de código, se você deseja implementar um analisador, esse tipo de gramática é mais simples e fácil de implementar. O C # segue essa regra em vários locais em que há uma lista de itens separados por vírgula, como itens em uma enumdefinição.

Iravanchi
fonte
1

Isso facilita a geração de código, pois você só precisa adicionar uma linha e não precisa adicionar a última entrada como se fosse um caso especial. Isso é especialmente verdade ao usar macros para gerar código. Há um esforço para tentar eliminar a necessidade de macros do idioma, mas grande parte do idioma evoluiu de mãos dadas com as macros disponíveis. A vírgula extra permite que macros como as seguintes sejam definidas e usadas:

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

Uso:

LIST_BEGIN
   LIST_ENTRY(1)
   LIST_ENTRY(2)
LIST_END

Esse é um exemplo muito simplificado, mas geralmente esse padrão é usado por macros para definir itens como tabelas e mapas de despacho, mensagem, evento ou tradução. Se uma vírgula não fosse permitida no final, precisaríamos de um especial:

#define LIST_LAST_ENTRY(x) x

e isso seria muito difícil de usar.

Scott Langham
fonte
0

Assim, quando duas pessoas adicionam um novo item em uma lista em ramificações separadas, o Git pode mesclar adequadamente as alterações, porque o Git funciona em uma linha.

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

Se você usar uma matriz sem comprimento especificado, o VC ++ 6.0 poderá identificar automaticamente seu comprimento; portanto, se você usar "int a [] = {1,2,};" o comprimento de a é 3, mas o último não tem ' não foi inicializado, você pode usar "cout <

zhi_jian
fonte
Isso é um bug do VC6 que não está em conformidade com o padrão?
Thomson