O main () é realmente o início de um programa C ++?

131

A seção $ 3.6.1 / 1 do padrão C ++ lê:

Um programa deve conter uma função global chamada main , que é o início designado do programa.

Agora considere este código,

int square(int i) { return i*i; }
int user_main()
{ 
    for ( int i = 0 ; i < 10 ; ++i )
           std::cout << square(i) << endl;
    return 0;
}
int main_ret= user_main();
int main() 
{
        return main_ret;
}

Este código de exemplo faz o que pretendo fazer, ou seja, imprimir o quadrado de números inteiros de 0 a 9, antes de entrar na main()função que deveria ser o "início" do programa.

Também o compilei com a -pedanticopção GCC 4.5.0. Não dá erro, nem aviso!

Então, minha pergunta é:

Esse código é realmente compatível com o padrão?

Se estiver em conformidade com o padrão, não invalida o que o Padrão diz? main()não é o início deste programa! user_main()executado antes do main().

Entendo que, para inicializar a variável global main_ret, o use_main()executa primeiro, mas isso é uma coisa completamente diferente; o ponto é que, isso não invalida a declaração citada $ 3.6.1 / 1 do Padrão, como main()não é o início do programa; é na verdade a fim de este programa!


EDITAR:

Como você define a palavra 'start'?

Tudo se resume à definição da frase "início do programa" . Então, como exatamente você o define?

Nawaz
fonte

Respostas:

85

Não, o C ++ faz muitas coisas para "definir o ambiente" antes da chamada do main; no entanto, main é o início oficial da parte "especificada pelo usuário" do programa C ++.

Algumas das configurações do ambiente não são controláveis ​​(como o código inicial para configurar std :: cout; no entanto, parte do ambiente é controlável como blocos globais estáticos (para inicializar variáveis ​​globais estáticas). controle anterior ao main, você não tem controle total da ordem em que os blocos estáticos são inicializados.

Após o main, seu código é conceitualmente "totalmente controlado" do programa, no sentido de que você pode especificar as instruções a serem executadas e a ordem em que elas serão executadas. O multiencadeamento pode reorganizar a ordem de execução do código; mas você ainda está no controle do C ++ porque especificou que seções do código são executadas (possivelmente) fora de ordem.

Edwin Buck
fonte
9
+1 para este "Observe que, como você não tem controle total antes do main, não tem controle total na ordem em que os blocos estáticos são inicializados. Depois do main, seu código é conceitualmente" totalmente controlado "de o programa, no sentido de que você pode especificar as instruções a serem executadas e a ordem na qual executá-las " . Isso também me faz para marcar esta resposta como resposta aceita ... penso que estes são pontos muito importantes, que suficientemente justifica main()como "início do programa"
Nawaz
13
@Nawaz: observe que, além de nenhum controle total sobre a ordem de inicialização, você não tem controle sobre os erros de inicialização: não é possível capturar exceções em um escopo global.
André Caron
@Nawaz: O que são blocos globais estáticos? você poderia explicar usando um exemplo simples? Obrigado
Destructor
@meet: Os objetos declarados no nível do espaço para nome têm staticduração de armazenamento e, como tal, esses objetos pertencentes a diferentes unidades de conversão podem ser inicializados em qualquer ordem (porque a ordem não é especificada pelo padrão). Não tenho certeza se isso responde à sua pergunta, embora seja o que eu poderia dizer no contexto deste tópico.
Nawaz 04/05
88

Você está lendo a frase incorretamente.

Um programa deve conter uma função global chamada main, que é o início designado do programa.

O padrão está DEFININDO a palavra "iniciar" para os fins do restante do padrão. Não diz que nenhum código é executado antes de mainser chamado. Diz que o início do programa é considerado como função main.

Seu programa é compatível. Seu programa não "iniciou" até que o principal seja iniciado. O construtor é chamado antes que o programa "inicie" de acordo com a definição de "start" no padrão, mas isso dificilmente importa. Um monte de código é executado antes mainé sempre chamado em cada programa, não apenas neste exemplo.

Para fins de discussão, o código do construtor é executado antes do 'início' do programa e é totalmente compatível com o padrão.

Adam Davis
fonte
3
Desculpe, mas discordo da sua interpretação dessa cláusula.
Lightness Races em Órbita
Eu acho que Adam Davis está certo, "main" é mais como algum tipo de restrição de codificação.
laike9m
@LightnessRacesinOrbit Eu nunca fiz o acompanhamento, mas para mim essa frase pode ser resumida como "uma função global chamada main é o início designado do programa" (grifo nosso). Qual é a sua interpretação dessa frase?
Adam Davis
1
@ AdamDavis: Não me lembro qual era minha preocupação. Não consigo pensar em um agora.
Lightness Races in Orbit (
23

Seu programa não será vinculado e, portanto, não será executado, a menos que haja um principal. No entanto, main () não causa o início da execução do programa, porque os objetos no nível do arquivo têm construtores que são executados anteriormente e seria possível escrever um programa inteiro que dura sua vida útil antes que main () seja atingido e permita que o próprio main tenha um corpo vazio.

Na realidade, para impor isso, você teria que ter um objeto construído antes do main e seu construtor para invocar todo o fluxo do programa.

Veja isso:

class Foo
{
public:
   Foo();

 // other stuff
};

Foo foo;

int main()
{
}

O fluxo do seu programa resultaria efetivamente de Foo::Foo()

CashCow
fonte
13
+1. Mas observe que se você tiver vários objetos globais em diferentes unidades de conversão, isso causará problemas rapidamente, pois a ordem na qual os construtores são chamados é indefinida. Você pode se safar de singletons e inicialização lenta, mas em um ambiente multithread, as coisas ficam muito feias rapidamente. Em uma palavra, não faça isso em código real.
Alexandre C.
3
Embora você provavelmente deva fornecer ao main () um corpo adequado no seu código e permitir que ele execute a execução, o conceito de objetos externos que iniciam é no que muitas bibliotecas LD_PRELOAD são baseadas.
precisa saber é o seguinte
2
@Alex: O padrão diz indefinido, mas como uma ordem prática de link de assunto (geralmente, dependendo do compilador) controla a ordem de inicialização.
precisa saber é o seguinte
1
@ Thomas: Eu certamente nem tentaria remotamente confiar nisso. Eu certamente também não tentaria controlar manualmente o sistema de compilação.
Alexandre C.
1
@ Alex: não é mais tão importante, mas no dia em que usamos o link para controlar a imagem de compilação, a fim de diminuir a paginação da memória física. Há outros motivos que você pode querer controlar a ordem de inicialização, mesmo que isso não afete a semântica do programa, como o teste de comparação do desempenho da inicialização.
precisa saber é o seguinte
15

Você marcou a pergunta como "C" também; portanto, falando estritamente sobre C, sua inicialização falhará conforme a seção 6.7.8 "Inicialização" do padrão ISO C99.

O mais relevante nesse caso parece ser a restrição 4, que diz:

Todas as expressões em um inicializador para um objeto que tenha duração de armazenamento estático devem ser expressões constantes ou literais de cadeia de caracteres.

Portanto, a resposta para sua pergunta é que o código não é compatível com o padrão C.

Você provavelmente desejaria remover a tag "C" se estivesse interessado apenas no padrão C ++.

Remo.D
fonte
4
@ Remo.D, você poderia nos dizer o que há nessa seção. Nem todos nós temos o padrão C :).
UmmaGumma
2
Desde que você é tão exigente: Infelizmente, o ANSI C é obsoleto desde 1989. ISO C90 ou C99 são os padrões relevantes a serem citados.
Lundin
@Lundin: Ninguém nunca é exigente o suficiente :) Eu estava lendo a ISO C99, mas estou bastante confiante de que ela também se aplica à C90.
precisa saber é o seguinte
@Um tiro. Você está certo, acrescentou a frase que acho mais relevante aqui.
precisa saber é o seguinte
3
@Remo: +1 por fornecer as informações de que não é válido C; eu não sabia disso. Veja como as pessoas aprendem, às vezes por plano, às vezes por acaso!
Nawaz
10

A Seção 3.6 como um todo é muito clara sobre a interação maine as inicializações dinâmicas. O "início designado do programa" não é usado em nenhum outro lugar e é apenas descritivo da intenção geral de main(). Não faz sentido interpretar essa frase de maneira normativa que contradiga os requisitos mais detalhados e claros da Norma.

aschepler
fonte
9

O compilador geralmente precisa adicionar código antes de main () para ser compatível com o padrão. Porque o padrão especifica que a initalização de globais / estáticas deve ser feita antes que o programa seja executado. E, como mencionado, o mesmo vale para os construtores de objetos colocados no escopo do arquivo (globais).

Portanto, a pergunta original também é relevante para C, porque em um programa em C você ainda teria a inicialização global / estática antes de iniciar o programa.

Os padrões assumem que essas variáveis ​​são inicializadas através de "magia", porque não dizem como devem ser definidas antes da inicialização do programa. Eu acho que eles consideraram isso algo fora do escopo de um padrão de linguagem de programação.

Editar: veja, por exemplo, ISO 9899: 1999 5.1.2:

Todos os objetos com duração de armazenamento estático devem ser inicializados (definidos com seus valores iniciais) antes da inicialização do programa. A maneira e o momento dessa inicialização não são especificados.

A teoria por trás de como essa "mágica" deveria ser feita remonta ao nascimento de C, quando era uma linguagem de programação destinada a ser usada apenas no SO UNIX, em computadores baseados em RAM. Em teoria, o programa seria capaz de carregar todos os dados pré-inicializados do arquivo executável na RAM, ao mesmo tempo em que o próprio programa era carregado na RAM.

Desde então, os computadores e o SO evoluíram, e o C é usado em uma área muito maior do que o inicialmente previsto. Um sistema operacional moderno para PC possui endereços virtuais, etc., e todos os sistemas embarcados executam código da ROM, não da RAM. Portanto, existem muitas situações em que a RAM não pode ser configurada "automaticamente".

Além disso, o padrão é muito abstrato para saber algo sobre pilhas e memória de processo etc. Essas coisas também devem ser feitas antes do início do programa.

Portanto, praticamente todo programa C / C ++ possui algum código init / "copy-down" que é executado antes que o main seja chamado, a fim de estar em conformidade com as regras de inicialização dos padrões.

Como exemplo, os sistemas embarcados geralmente têm uma opção chamada "inicialização não compatível com ISO", em que toda a fase de inicialização é ignorada por motivos de desempenho e, em seguida, o código é iniciado diretamente do principal. Mas esses sistemas não estão em conformidade com os padrões, pois você não pode confiar nos valores init de variáveis ​​globais / estáticas.

Lundin
fonte
4

Seu "programa" simplesmente retorna um valor de uma variável global. Tudo o resto é código de inicialização. Portanto, o padrão é válido - você apenas tem um programa muito trivial e uma inicialização mais complexa.

Zac Howland
fonte
2

Parece uma semântica em inglês. O OP se refere ao seu bloco de código primeiro como "código" e depois como "programa". O usuário escreve o código e, em seguida, o compilador grava o programa.

dSerk
fonte
1

main é chamado após a inicialização de todas as variáveis ​​globais.

O que o padrão não especifica é a ordem de inicialização de todas as variáveis ​​globais de todos os módulos e bibliotecas vinculadas estaticamente.

vz0
fonte
0

Sim, main é o "ponto de entrada" de todos os programas C ++, exceto as extensões específicas da implementação. Mesmo assim, algumas coisas acontecem antes da inicialização principal, principalmente a global, como para main_ret.

Fred Nurk
fonte