#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
Isso chama indiretamente main
? quão?
#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
Isso chama indiretamente main
? quão?
Respostas:
A linguagem C define o ambiente de execução em duas categorias: independente e hospedado . Em ambos os ambientes de execução, uma função é chamada pelo ambiente para a inicialização do programa.
Em um ambiente autônomo, a função de inicialização do programa pode ser definida pela implementação, enquanto no ambiente hospedado deveria ser main
. Nenhum programa em C pode ser executado sem a função de inicialização do programa nos ambientes definidos.
No seu caso, main
está oculto pelas definições do pré-processador. begin()
irá expandir para o decode(a,n,i,m,a,t,e)
qual ainda será expandido main
.
int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main()
decode(s,t,u,m,p,e,d)
é uma macro parametrizada com 7 parâmetros. A lista de substituição para esta macro é m##s##u##t
. m, s, u
e t
são 4º , 1º , 3º e 2º parâmetros usados na lista de substituição.
s, t, u, m, p, e, d
1 2 3 4 5 6 7
O descanso é inútil ( apenas para ofuscar ). O argumento passado para decode
é " a , n , i , m , a, t, e", portanto, os identificadores m, s, u
e t
são substituídos pelos argumentos m, a, i
e n
, respectivamente.
m --> m
s --> a
u --> i
t --> n
_start()
. Ou ainda em um nível mais baixo, posso tentar apenas alinhar o início do meu programa com o endereço para o qual o IP é definido após a inicialização. main()
é a biblioteca C Standard . O próprio C não impõe restrições sobre isso.
decode(a,n,i,m,a,t,e)
se tornou m##a##i##n
? Substitui personagens? Você pode fornecer um link para a documentação da decode
função? Obrigado.
begin
é definido para ser substituído pelo decode(a,n,i,m,a,t,e)
que foi definido antes. Esta função pega os argumentos s,t,u,m,p,e,d
e os concatena nesta forma m##s##u##t
( ##
significa concatenar). Ou seja, ele ignora os valores de p, e e d. Conforme você "chama" decode
com s = a, t = n, u = i, m = m, ele efetivamente substitui begin
por main
.
Tente usar gcc -E source.c
, a saída termina com:
int main()
{
printf("Ha HA see how it is?? ");
}
Portanto, uma main()
função é gerada pelo pré-processador.
O programa em questão faz chamada main()
devido à expansão macro, mas o seu pressuposto é falho - que não tem que chamar main()
em tudo!
A rigor, você pode ter um programa C e compilá-lo sem ter um main
símbolo. main
é algo para o qual o c library
espera saltar, depois de concluir sua própria inicialização. Normalmente, você salta para a main
partir do símbolo libc conhecido como _start
. É sempre possível ter um programa muito válido, que simplesmente execute assembly, sem ter um principal. Dê uma olhada neste:
/* This must be compiled with the flag -nostdlib because otherwise the
* linker will complain about multiple definitions of the symbol _start
* (one here and one in glibc) and a missing reference to symbol main
* (that the libc expects to be linked against).
*/
void
_start ()
{
/* calling the write system call, with the arguments in this order:
* 1. the stdout file descriptor
* 2. the buffer we want to print (Here it's just a string literal).
* 3. the amount of bytes we want to write.
*/
asm ("int $0x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
asm ("int $0x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}
Compile o acima com gcc -nostdlib without_main.c
e veja a impressão Hello World!
na tela apenas emitindo chamadas de sistema (interrupções) em assembly embutido.
Para obter mais informações sobre este problema específico, verifique o blog ksplice
Outra questão interessante, é que você também pode ter um programa que compila sem que o main
símbolo corresponda a uma função C. Por exemplo, você pode ter o seguinte como um programa C muito válido, que só faz o compilador reclamar quando você sobe o nível de Avisos.
/* These values are extracted from the decimal representation of the instructions
* of a hello world program written in asm, that gdb provides.
*/
const int main[] = {
-443987883, 440, 113408, -1922629632,
4149, 899584, 84869120, 15544,
266023168, 1818576901, 1461743468, 1684828783,
-1017312735
};
Os valores na matriz são bytes que correspondem às instruções necessárias para imprimir Hello World na tela. Para um relato mais detalhado de como esse programa específico funciona, dê uma olhada neste post do blog , que é onde eu também o li primeiro.
Quero fazer um último aviso sobre esses programas. Não sei se eles se registram como programas C válidos de acordo com a especificação da linguagem C, mas compilá-los e executá-los é certamente muito possível, mesmo que eles violem a própria especificação.
_start
parte de um padrão definido ou é apenas específico da implementação? Certamente o seu "principal como um array" é específico da arquitetura. Também importante, não seria irracional que seu truque "principal como um array" falhasse em tempo de execução devido a restrições de segurança (embora isso seja mais provável se você não usar o const
qualificador, e muitos sistemas ainda permitiriam).
_start
não está no padrão ELF, embora o AMD64 psABI contenha uma referência _start
em 3.4 Inicialização do processo . Oficialmente, ELF só conhece o endereço em e_entry
no cabeçalho ELF, _start
é apenas um nome que a implementação escolheu.
const
não importa nem um pouco - o nome do símbolo nesse arquivo executável binário é main
. Nem mais nem menos. const
é uma construção C que não significa nada em tempo de execução.
Alguém está tentando agir como mágico. Ele acha que pode nos enganar. Mas todos nós sabemos, a execução do programa c começa com main()
.
O int begin()
será substituído decode(a,n,i,m,a,t,e)
por uma passagem do estágio de pré-processador. Então, novamente, decode(a,n,i,m,a,t,e)
será substituído por m ## a ## i ## n. Como por associação posicional de chamada de macro, s
vontade tem um valor de caráter a
. Da mesma forma, u
será substituído por 'i' e t
será substituído por 'n'. E é assim que m##s##u##t
vai se tornarmain
Quanto ao ##
símbolo na expansão da macro, é o operador de pré-processamento e realiza a colagem do token. Quando uma macro é expandida, os dois tokens de cada lado de cada operador '##' são combinados em um único token, que então substitui o '##' e os dois tokens originais na expansão da macro.
Se você não acredita em mim, você pode compilar seu código com -E
flag. Isso interromperá o processo de compilação após o pré-processamento e você poderá ver o resultado da colagem do token.
gcc -E FILENAME.c
decode(a,b,c,d,[...])
embaralha os primeiros quatro argumentos e os junta para obter um novo identificador, na ordem dacb
. (Os três argumentos restantes são ignorados.) Por exemplo, decode(a,n,i,m,[...])
fornece o identificador main
. Observe que é assim que a begin
macro é definida.
Portanto, a begin
macro é simplesmente definida como main
.
Em seu exemplo, a main()
função está realmente presente, porque begin
é uma macro que o compilador substitui por decode
macro que por sua vez é substituída pela expressão m ## s ## u ## t. Usando expansão macro ##
, você alcançará a palavra main
de decode
. Este é um traço:
begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main
É apenas um truque para se ter main()
, mas usar o nome main()
para a função de entrada do programa não é necessário na linguagem de programação C. Depende de seus sistemas operacionais e do vinculador como uma de suas ferramentas.
No Windows, você nem sempre usa main()
, mas sim WinMain
ouwWinMain
, embora possa usar main()
, mesmo com o conjunto de ferramentas da Microsoft . No Linux, pode-se usar _start
.
Cabe ao vinculador, como ferramenta do sistema operacional, definir o ponto de entrada, e não o idioma em si. Você pode até definir nosso próprio ponto de entrada e criar uma biblioteca que também seja executável !
main()
função à linguagem de programação C, o que não é correto.