É 'int main;' um programa C / C ++ válido?

113

Eu pergunto porque meu compilador parece pensar assim, embora eu não.

echo 'int main;' | cc -x c - -Wall
echo 'int main;' | c++ -x c++ - -Wall

O Clang não emite nenhum aviso ou erro com isso, e o gcc emite apenas o aviso manso:, 'main' is usually a function [-Wmain]mas apenas quando compilado como C. Especificar um -std=não parece importar.

Caso contrário, ele compila e vincula corretamente. Mas na execução, ele termina imediatamente com SIGBUS(para mim).

Lendo as (excelentes) respostas em O que main () deve retornar em C e C ++? e um rápido grep pelas especificações da linguagem, certamente me pareceria que uma função principal é necessária. Mas a verborragia do gcc's -Wmain('main' geralmente é uma função) (e a falta de erros aqui) parece possivelmente sugerir o contrário.

Mas por que? Existe algum uso estranho ou “histórico” para isso? Alguém sabe o que dá?

Meu ponto, suponho, é que realmente acho que isso deveria ser um erro em um ambiente hospedado, hein?

Geoff Nixon
fonte
6
Para tornar o gcc um (principalmente) compilador compatível com o padrão, você precisagcc -std=c99 -pedantic ...
pmg
3
@pmg É o mesmo aviso, com ou sem -pedanticou nenhum -std. Meu sistema c99também compila isso sem aviso ou erro ...
Geoff Nixon
3
Infelizmente, se você for "inteligente o suficiente", poderá criar coisas que são aceitáveis ​​para o compilador, mas não fazem sentido. Nesse caso, você está vinculando a biblioteca de tempo de execução C para chamar uma variável chamada main, que provavelmente não funcionará. Se você inicializar main com o valor "correto", ele pode realmente retornar ...
Mats Petersson
7
E mesmo que seja válido, é uma coisa horrível de se fazer (código ilegível). BTW, pode ser diferente em implementações hospedadas e em implementações independentes (que não conhecemos main)
Basile Starynkevitch
1
Para momentos mais divertidos, experimentemain=195;
imallett

Respostas:

97

Uma vez que a pergunta está marcada duas vezes como C e C ++, o raciocínio para C ++ e C seria diferente:

  • C ++ usa o nome mutilado para ajudar o linker a distinguir entre símbolos textualmente idênticos de diferentes tipos, por exemplo, uma variável global xyze uma função global independente xyz(int). No entanto, o nome mainnunca é mutilado.
  • C não usa mutilação, portanto, é possível para um programa confundir o linker fornecendo um símbolo de um tipo no lugar de um símbolo diferente, e fazer o link do programa com êxito.

Isso é o que está acontecendo aqui: o vinculador espera encontrar o símbolo main, e ele o faz. Ele "liga" aquele símbolo como se fosse uma função, porque não o conhece melhor. A parte da biblioteca de tempo de execução que passa o controle para mainsolicitar ao vinculador main, portanto, o vinculador fornece o símbolo main, permitindo que a fase de vinculação seja concluída. É claro que isso falha em tempo de execução, porque mainnão é uma função.

Aqui está outra ilustração do mesmo problema:

arquivo xc:

#include <stdio.h>
int foo(); // <<== main() expects this
int main(){
    printf("%p\n", (void*)&foo);
    return 0;
}

arquivo yc:

int foo; // <<== external definition supplies a symbol of a wrong kind

compilando:

gcc x.c y.c

Isso compila e provavelmente seria executado, mas é um comportamento indefinido, porque o tipo de símbolo prometido ao compilador é diferente do símbolo real fornecido ao vinculador.

No que diz respeito ao aviso, acho que é razoável: C permite construir bibliotecas que não têm mainfunção, portanto, o compilador libera o nome mainpara outros usos se você precisar definir uma variável mainpor algum motivo desconhecido.

dasblinkenlight
fonte
3
Porém, o compilador C ++ trata a função principal de maneira diferente. Seu nome não é mutilado mesmo sem o externo "C". Eu acho que é porque caso contrário ele precisaria emitir seu próprio extern "C" principal, para garantir a ligação.
UldisK
@UldisK Sim, eu mesmo percebi isso e achei bastante interessante. Faz sentido, mas nunca pensei nisso.
Geoff Nixon
2
Na verdade, os resultados para C ++ e C não são diferentes, como apontado aqui - mainnão está sujeito a alteração de nome (ao que parece) em C ++, seja ou não uma função.
Geoff Nixon
4
@nm Eu acho que sua interpretação da questão é muito restrita: além de fazer a pergunta no título do post, OP claramente busca uma explicação de por que seu programa compilou em primeiro lugar ("meu compilador parece pensar assim, embora eu não "), bem como uma sugestão de por que pode ser útil definir maincomo qualquer coisa diferente de uma função. A resposta oferece uma explicação para ambas as partes.
dasblinkenlight
1
O fato de o símbolo principal não estar sujeito a alteração de nome é irrelevante. Não há menção de mutilação de nome no padrão C ++. A mutilação de nomes é um problema de implementação.
David Hammen
30

mainnão é uma palavra reservada é apenas um identificador predefinido (como cin, endl, npos...), então você poderia declarar uma variável chamada main, inicialize-o e, em seguida, imprimir o seu valor.

Claro:

  • o aviso é útil, pois é bastante sujeito a erros;
  • você pode ter um arquivo fonte sem a main()função (bibliotecas).

EDITAR

Algumas referências:

  • main não é uma palavra reservada (C ++ 11):

    A função mainnão deve ser usada dentro de um programa. A ligação (3.5) de mainé definida pela implementação. Um programa que define principal como excluídos ou que declara principal a ser inline, staticou constexpré mal-formado. O nome mainnão é reservado de outra forma. [Exemplo: funções de membro, classes e enumerações podem ser chamadas main, assim como entidades em outros namespaces. - exemplo final]

    C ++ 11 - [basic.start.main] 3.6.1.3

    [2.11 / 3] [...] alguns identificadores são reservados para uso por implementações C ++ e bibliotecas padrão (17.6.4.3.2) e não devem ser usados ​​de outra forma; nenhum diagnóstico é necessário.

    [17.6.4.3.2 / 1] Certos conjuntos de nomes e assinaturas de função são sempre reservados para a implementação:

    • Cada nome que contém um sublinhado duplo __ ou começa com um sublinhado seguido por uma letra maiúscula (2.12) é reservado para a implementação para qualquer uso.
    • Cada nome que começa com um sublinhado é reservado para a implementação para uso como um nome no namespace global.
  • Palavras reservadas em linguagens de programação .

    As palavras reservadas podem não ser redefinidas pelo programador, mas os predefineds geralmente podem ser substituídos em alguma capacidade. É o caso de main: existem escopos em que uma declaração que usa esse identificador redefine seu significado.

manlio
fonte
- Acho que estou bastante enganado pelo fato de que (já que é tão sujeito a erros), por que isso é um aviso (não um erro), e por que é apenas um aviso quando compilado como C - Claro, você pode compilar sem uma main()função, mas você não pode vinculá-la como um programa. O que está acontecendo aqui é que um programa "válido" está sendo vinculado sem um main(), apenas um main.
Geoff Nixon
7
cine endlnão estão no namespace padrão - eles estão no stdnamespace. nposé um membro de std::basic_string.
AnotherParker
1
main é reservado como um nome global. Nenhuma das outras coisas que você mencionou, nem main, são predefinidas.
Potatoswatter
1
Consulte C ++ 14 §3.6.1 e C11 §5.1.2.2.1 para obter as limitações do que mainé permitido. C ++ diz "Uma implementação não deve predefinir a função principal" e C diz "A implementação não declara nenhum protótipo para esta função."
Potatoswatter
@manlio: por favor, esclareça o que você está citando. Quanto ao simples C, as citações estão erradas. Então eu acho que é qualquer um dos padrões c ++, não é?
dhein
19

É int main;um programa C / C ++ válido?

Não está totalmente claro o que é um programa C / C ++.

É int main;um programa C válido?

Sim. Uma implementação independente é permitida para aceitar tal programa. mainnão precisa ter nenhum significado especial em um ambiente independente.

É não válido em um ambiente hospedado.

É int main;um programa C ++ válido?

Idem.

Por que ele trava?

O programa não precisa fazer sentido em seu ambiente. Em um ambiente independente, a inicialização e o encerramento do programa, e o significado de main, são definidos pela implementação.

Por que o compilador me avisa?

O compilador pode avisá-lo sobre o que quiser, desde que não rejeite programas em conformidade. Por outro lado, o aviso é tudo o que é necessário para diagnosticar um programa não conforme. Uma vez que esta unidade de tradução não pode fazer parte de um programa hospedado válido, uma mensagem de diagnóstico é justificada.

É gccum ambiente independente ou hospedado?

Sim.

gccdocumenta o -ffreestandingsinalizador de compilação. Adicione e o aviso desaparece. Você pode querer usá-lo ao construir, por exemplo, kernels ou firmware.

g++não documenta tal bandeira. O fornecimento parece não ter efeito sobre o programa. É provavelmente seguro presumir que o ambiente fornecido pelo g ++ está hospedado. A ausência de diagnóstico neste caso é um bug.

n. 'pronomes' m.
fonte
17

É um aviso, pois não é tecnicamente proibido. O código de inicialização usará a localização do símbolo "principal" e saltará para ela com os três argumentos padrão (argc, argv e envp). Isso não acontece, e no momento do link não pode verificar se é realmente uma função, nem mesmo se tem esses argumentos. É também por isso que int main (int argc, char ** argv) funciona - o compilador não sabe sobre o argumento envp e ele simplesmente não é usado, e é uma limpeza do chamador.

Como uma piada, você poderia fazer algo como

int main = 0xCBCBCBCB;

em uma máquina x86 e, ignorando avisos e coisas semelhantes, ele não apenas compilará, mas também funcionará.

Alguém usou uma técnica semelhante a esta para escrever um executável (mais ou menos) que roda em múltiplas arquiteturas diretamente - http://phrack.org/issues/57/17.html#article . Também foi usado para vencer o IOCCC - http://www.ioccc.org/1984/mullender/mullender.c .

elegante
fonte
1
"É um aviso, pois não é tecnicamente proibido" - é inválido em C ++.
Saúde e hth. - Alf
3
"os três argumentos padrão (argc, argv e envp)" - aqui você está possivelmente falando sobre o padrão Posix.
Saúde e hth. - Alf
No meu sistema (Ubuntu 14 / x64), a seguinte linha funciona com o gcc:int main __attribute__ ((section (".text")))= 0xC3C3C3C3;
csharpfolk
@ Cheersandhth.-Alf Os dois primeiros são padrão, o terceiro é POSIX.
dascandy
9

É um programa válido?

Não.

Não é um programa, pois não possui partes executáveis.

É válido compilar?

Sim.

Pode ser usado com um programa válido?

Sim.

Nem todo código compilado precisa ser executável para ser válido. Exemplos são bibliotecas estáticas e dinâmicas.

Você efetivamente construiu um arquivo de objeto. Não é um executável válido; no entanto, outro programa pode se vincular ao objeto mainno arquivo resultante, carregando-o em tempo de execução.

Isso deve ser um erro?

Tradicionalmente, C ++ permite que o usuário faça coisas que podem parecer não ter uso válido, mas que se encaixam na sintaxe da linguagem.

Claro, isso poderia ser reclassificado como um erro, mas por quê? Que propósito isso serviria, mas o aviso não?

Enquanto houver uma possibilidade teórica de essa funcionalidade ser usada no código real, é muito improvável que ter um objeto não funcional chamado mainresulte em um erro de acordo com a linguagem.

Michael Gazonda
fonte
Ele cria um símbolo externamente visível denominado main. Como pode um programa válido, que deve ter uma função externamente visível chamada main, vincular a ele?
Keith Thompson
@KeithThompson Load at runtime. Vai esclarecer.
Michael Gazonda
Pode porque não é capaz de dizer a diferença entre os tipos de símbolo. A vinculação funciona muito bem - a execução (exceto no caso cuidadosamente elaborado) não.
Chris Stratton
1
@ChrisStratton: Acho que o argumento de Keith é que a vinculação falha porque o símbolo é definido de forma múltipla ... porque o "programa válido" não seria um programa válido a menos que definisse uma mainfunção.
Ben Voigt
@BenVoigt Mas se aparecer em uma biblioteca, então a vinculação não falhará (e provavelmente não poderá), porque no tempo de vinculação do programa, a int main;definição não estará visível.
6

Eu gostaria de acrescentar às respostas já fornecidas, citando os padrões de linguagem atuais.

É 'int main;' um programa C válido?

Resposta curta (minha opinião): somente se sua implementação usar um "ambiente de execução independente".

Todas as citações seguintes de C11

5. Meio Ambiente

Uma implementação traduz arquivos fonte C e executa programas C em dois ambientes de sistema de processamento de dados, que serão chamados de ambiente de tradução e ambiente de execução [...]

5.1.2 Ambientes de execução

Dois ambientes de execução são definidos: independente e hospedado. Em ambos os casos, a inicialização do programa ocorre quando uma função C designada é chamada pelo ambiente de execução.

5.1.2.1 Ambiente autônomo

Em um ambiente independente (no qual a execução do programa C pode ocorrer sem nenhum benefício de um sistema operacional), o nome e o tipo da função chamada na inicialização do programa são definidos pela implementação.

5.1.2.2 Ambiente hospedado

Um ambiente hospedado não precisa ser fornecido, mas deve estar em conformidade com as seguintes especificações, se houver.

5.1.2.2.1 Inicialização do programa

A função chamada na inicialização do programa é chamada de principal . [...] Deve ser definido com um tipo de retorno de int e sem parâmetros [...] ou com dois parâmetros [...] ou equivalente ou de alguma outra maneira definida pela implementação.

Destes, o seguinte é observado:

  • Um programa C11 pode ter um ambiente de execução independente ou hospedado e ser válido.
  • Se tiver um autônomo, não precisa haver uma função principal.
  • Caso contrário, deve haver um com um valor de retorno do tipo int .

Em um ambiente de execução independente, eu diria que é um programa válido que não permite que a inicialização aconteça, porque não há nenhuma função presente para isso, conforme exigido em 5.1.2. Em um ambiente de execução hospedado, embora seu código introduza um objeto denominado main , ele não pode fornecer um valor de retorno, então eu argumentaria que não é um programa válido neste sentido, embora alguém pudesse argumentar antes que se o programa não fosse destinado a ser executado (em pode querer fornecer dados apenas, por exemplo), então ele simplesmente não permite fazer exatamente isso.

É 'int main;' um programa C ++ válido?

Resposta curta (minha opinião): somente se sua implementação usar um "ambiente de execução independente".

Citação de C ++ 14

3.6.1 Função principal

Um programa deve conter uma função global chamada main, que é o início designado do programa. É definido pela implementação se um programa em um ambiente independente é necessário para definir uma função principal. [...] Deve obrigatoriamente ter um tipo de retorno do tipo int, mas caso contrário, seu tipo é definido pela implementação. [...] O nome main não é reservado de outra forma.

Aqui, ao contrário do padrão C11, menos restrições se aplicam ao ambiente de execução independente, já que nenhuma função de inicialização é mencionada, enquanto para um ambiente de execução hospedado, o caso é praticamente o mesmo que para C11.

Novamente, eu diria que, para o caso hospedado, seu código não é um programa C ++ 14 válido, mas tenho certeza de que é para o caso independente.

Como minha resposta considera apenas o ambiente de execução , acho que a resposta de dasblinkenlicht entra em cena, já que a mutilação de nomes que ocorre no ambiente de tradução ocorre de antemão. Aqui, não estou tão certo de que as citações acima sejam observadas tão estritamente.

Ingo Schalk-Schupp
fonte
4

Meu ponto, suponho, é que realmente acho que isso deveria ser um erro em um ambiente hospedado, hein?

O erro é seu. Você não especificou uma função chamada mainque retorna um inte tentou usar seu programa em um ambiente hospedado.

Suponha que você tenha uma unidade de compilação que defina uma variável global chamada main. Isso pode ser legal em um ambiente independente porque o que constitui um programa é deixado para a implementação em ambientes independentes.

Suponha que você tenha outra unidade de compilação que define uma função global chamada mainque retorna um inte não recebe argumentos. Isso é exatamente o que um programa em um ambiente hospedado precisa.

Tudo está bem se você usar apenas a primeira unidade de compilação em um ambiente independente e apenas usar a segunda em um ambiente hospedado. E se você usar os dois em um programa? Em C ++, você violou a regra de uma definição. Esse é um comportamento indefinido. Em C, você violou a regra que determina que todas as referências a um único símbolo devem ser consistentes; se não forem, é um comportamento indefinido. O comportamento indefinido é um "saia da prisão, grátis!" cartão para desenvolvedores de uma implementação. Qualquer coisa que uma implementação faça em resposta a um comportamento indefinido é compatível com o padrão. A implementação não precisa avisar, muito menos detectar, comportamento indefinido.

E se você usar apenas uma dessas unidades de compilação, mas usar a errada (que é o que você fez)? Em C, a situação é clara. A falha em definir a função mainem uma das duas formas padrão em um ambiente hospedado é um comportamento indefinido. Suponha que você não tenha definido mainnada. O compilador / vinculador não precisa dizer nada sobre esse erro. Que eles se queixem é uma gentileza da parte deles. Que o programa C compilado e vinculado sem erros é sua culpa, não do compilador.

É um pouco menos claro em C ++ porque a falha em definir a função mainem um ambiente hospedado é um erro, e não um comportamento indefinido (em outras palavras, deve ser diagnosticado). No entanto, a única regra de definição em C ++ significa que os vinculadores podem ser bastante burros. O trabalho do vinculador é resolver referências externas e, graças à regra de definição única, o vinculador não precisa saber o que esses símbolos significam. Você forneceu um símbolo denominado main, o vinculador está esperando ver um símbolo denominado main, portanto, está tudo certo no que diz respeito ao vinculador.

David Hammen
fonte
4

Para C, até agora, é o comportamento definido pela implementação.

Conforme a ISO / IEC9899 diz:

5.1.2.2.1 Inicialização do programa

1 A função chamada na inicialização do programa é chamada de principal. A implementação não declara nenhum protótipo para esta função. Deve ser definido com um tipo de retorno de int e sem parâmetros:

int main(void) { /* ... */ }

ou com dois parâmetros (referidos aqui como argc e argv, embora quaisquer nomes possam ser usados, pois são locais para a função em que são declarados):

int main(int argc, char *argv[]) { /* ... */ }

ou equivalente; ou de alguma outra maneira definida pela implementação.

dhein
fonte
3

Não, este não é um programa válido.

Para C ++, isso foi recentemente explicitamente malformado pelo relatório de defeito 1886: Link de linguagem para main () que diz:

Não parece haver nenhuma restrição em dar a main () um link de linguagem explícito, mas provavelmente deve ser malformado ou suportado condicionalmente.

e parte da resolução incluiu a seguinte alteração:

Um programa que declara uma variável main no escopo global ou que declara o nome main com a ligação da linguagem C (em qualquer namespace) está mal formado.

Podemos encontrar esse texto no último rascunho do padrão C ++ N4527, que é o rascunho C ++ 1z.

As versões mais recentes do clang e do gcc agora tornam isso um erro ( veja ao vivo ):

error: main cannot be declared as global variable
int main;
^

Antes deste relatório de defeito, era um comportamento indefinido que não requer um diagnóstico. Por outro lado, um código malformado requer um diagnóstico, o compilador pode tornar isso um aviso ou um erro.

Shafik Yaghmour
fonte
Obrigado pela atualização! É ótimo ver que isso agora está começando com os diagnósticos do compilador. No entanto, devo dizer que acho as mudanças no padrão C ++ confusas. (Para obter informações básicas, consulte os comentários acima sobre a mutilação de nome main().) Eu entendo a razão para não permitir main()uma especificação de ligação explícita, mas não entendo que obrigue a main()ter uma ligação C ++ . É claro que a norma não diretamente endereço como lidar com ABI ligação / desconfiguração do nome, mas na prática (por exemplo, com Itanium ABI) isso iria mangle main()para _Z4mainv. o que estou perdendo?
Geoff Nixon
Acho que o comentário do supercat cobre isso. Se a implementação estiver fazendo suas próprias coisas antes de chamar o principal definido pelo usuário, ela pode facilmente escolher chamar um nome mutilado.
Shafik Yaghmour