Como a função main pega dos parâmetros vindos do terminal?

1. Como a função main pega dos parâmetros vindos do terminal?

Danilo Marto
danilomarto

(usa Debian)

Enviado em 22/08/2020 - 07:30h

Para a minha pergunta ficar mais clara. Suponha que o meu programa, chamado main, precise receber uma quantidade qualquer de parâmetros do console.

Para tanto, eu teria que fazer isso.


int main( int argc, char *argv[] ){
...
return 0;
}


Correto?

Em seguida eu compilo e executo passando os seus parâmetros.


$ gcc main.c -o main

$ ./main par1 par2 ... parn


Eu gostaria de saber como a função main consegue pegar a quantidade de parâmetros passados (que pode ser arbitrária) e jogar na variável argc e jogar todos eles dentro do array *argv[]?

Mais ainda, tem como reproduzir esse comportamento em uma função ordinária? Caso sim, Como? Poderia me fornecer um exemplo.

Faço essa pergunta por que eu estou estudando C e quero entender esse comportamento.



  


2. Re: Como a função main pega dos parâmetros vindos do terminal?

Paulo
paulo1205

(usa Ubuntu)

Enviado em 27/08/2020 - 03:52h

danilomarto escreveu:

Para a minha pergunta ficar mais clara. Suponha que o meu programa, chamado main, precise receber uma quantidade qualquer de parâmetros do console.

Para tanto, eu teria que fazer isso.

int main( int argc, char *argv[] ){
...
return 0;
}


Correto?


Sim.

Em seguida eu compilo e executo passando os seus parâmetros.

$ gcc main.c -o main

$ ./main par1 par2 ... parn


Eu gostaria de saber como a função main consegue pegar a quantidade de parâmetros passados (que pode ser arbitrária) e jogar na variável argc e jogar todos eles dentro do array *argv[]?


O shell interpreta o que você coloca na linha de comando e considera que um ou mais espaços (que não estejam cercados por aspas ou apóstrofos ou escapados por uma barra invertida) funcionam como separadores de argumentos. Esses argumentos são, então, colocados pelo shell dentro de um array, e esse array é passado como argumento para a chamada ao sistema execve() (junto com outro array, que contém as variáveis de ambiente). Essa chamada dispara a execução de um novo comando, e coloca os dois arrays numa região de memória que é entregue ao programa.

Quando o programa começa a executar, existe um código que executa antes de main() (sim, existe um código que executa dentro do seu programa antes de main(), que o linker coloca no executável na hora em que o produz) associa essas regiões de memória ao array recebido através de argv e, no caso das variáveis de ambiente, ao array global (declarado como pointeiro) environ.

Ou seja, o shell provavelmente tem algum código parecido com o seguinte.

char *command, **new_args=NULL, **new_environ=NULL;
ptrdiff_t token_offset=0;

// Extrai o comando e a lista de argumentos.
command=get_token(cmd_line, &token_offset); // Extrai o primeiro token da linha de comando (get_token é um nome fictício, mas possível de ser feito).
if(command==NULL){
fprintf(stderr, "Erro: ponteiro nulo ao tentar extrair comando.\n";
return -1;
}

size_t n_args=1;
new_args=reallocarray(new_args, n_args+1, sizeof *new_args);
if(new_args==NULL){
fprintf(stderr, "Não foi possível alocar memória: %s.\n", strerror(errno);
return -1;
}
new_args[0]=command;
new_args[1]=NULL;

char *next_arg;
while((next_arg=get_token(cmd_line, &token_offset))!=NULL){
++n_args;
void *new_ptr=reallocarray(new_args, n_args+1, sizeof *new_args);
if(new_ptr==NULL){
fprintf(stderr, "Não foi possível alocar memória: %s.\n", strerror(errno);
free(new_args);
return -1;
}
new_args=new_ptr;
new_args[n_args-1]=next_arg;
new_args[n_args]=NULL;
}

// Para as variáveis de ambiente, poderia também ser um laço de repetição varrendo alguma coisa, mas eu
// preferi colocar com valores fixos para ilustrar (poderiam ser valores fixos para os argumentos, também).
new_environ=malloc((N_ENVVARS+1)*sizeof *new_environ);
new_environ[0]="PATH=/bin:/usr/bin:/usr/local/bin";
new_environ[1]="HOME=/home/paulo1205";
new_environ[2]="TZ=America/Sao_Paulo";
/* ... */
new_environ[N_ENVVARS-1]="ULTIMA_VARIAVEL=ultimo valor";
new_environ[N_ENVVARS]=NULL; // O último elemento tem de ser um ponteiro nulo.

execve(caminho_executavel, new_args, new_environ);


Com a chamada acima, o programa que vier a ser executado terá n_args entregue como valor de argc, e os argumentos dispostos em argv vão corresponder exatamente ao que tiver sido colocado no array new_args no código acima, incluindo o n_args+1-ésimo (ou argc+1-ésimo) elemento, que é o ponteiro nulo. Semelhantemente, os valores dispostos no código acima no array new_environ vão aparecer no programa no array global environ, incluindo também o ponteiro nulo para demarcar o final da lista de argumentos.

Há duas formas, portanto, de percorrer os argumentos recebidos em argv, mostrados abaixo.

for(int n=0; n<argc; ++n)
puts(argv[n])

for(char **parg=argv; *parg!=NULL; ++parg)
puts(*parg);


De modo parecido com o segundo, você pode percorrer as variáveis de ambiente.

extern char **environ;

void print_env(void){
for(char **penv=environ; *penv!=NULL; ++penv)
puts(*penv);
}


Mais ainda, tem como reproduzir esse comportamento em uma função ordinária? Caso sim, Como? Poderia me fornecer um exemplo.


Acho que está bem explicado acima. A única coisa que eu não detalhei foi a implementação interna de get_token(), mas isso é relativamente fácil de fazer. E você pode até mesmo usar funções da biblioteca padrão para tanto, tais como strtok(), strsep() ou mesmo sscanf().


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner
Linux banner
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts