scanf %m não compreendi [RESOLVIDO]

1. scanf %m não compreendi [RESOLVIDO]

Mr.Perfection
perfection

(usa Slackware)

Enviado em 09/03/2020 - 18:47h

Me deparei com um código que eu anotei de algum lugar na Internet para algum tipo de teste que eu estava fazendo que não lembro hoje.
Então apagando os códigos que não usarei, este era um deles, me deparei com uma opção do scanf que não estou conseguindo compreender.
%m 

Lendo no man do scanf ele diz que isso seria um tipo de conversão de alocação dinâmica.
BUM! Então a coisa piorou de vez para mim.

O Pedaço de código que estou analisando é esse:
   char *nome;
scanf("%m[^\n]", &nome); // lê linha, alocando o espaço necessário
printf("Bom dia, %s\n", nome);
printf("Size of de nome = %d\n", sizeof(nome));

No momento não tenho obviamente uma aplicação para isso, porém meu objetivo é entender o que é isso já que passou pela minha mão.

DÚVIDAS:
Porque usar o %m ?
Para que tipo de situação isso seria necessário?
Não entendi que tipo de conversão ele está fazendo, imaginando que seja uma conversão.
Não entendi porque alocar memória, imaginando que ele esteja fazendo isso.

ADICIONAL
Ainda lendo na Internet parece que entendi o seguinte:
char word[32];
scanf("%31s", word); // Entendo que aloquei 31 espaços para a palavra digitada

char *word;
scanf("%ms", &word);
// Eu estaria alocando a Qtd de espaços de acordo com o que foi digitado?
// Exemplo: No caso VIVA eu alocaria 4 Espaços? Seria isso?

O QUE GERA AINDA MAIS DÚVIDA
Eu defini para mim, usar o scanf da forma abaixo:
char Texto[15]; scanf("%14[^\n]s", Texto); while(getchar() != '\n'); 

Será então que eu deveria ou seria melhor que eu usa-se ponteiro?
char *Texto; scanf("%m[^\n]s", &Texto); while(getchar() != '\n'); 



  


2. MELHOR RESPOSTA

Paulo
paulo1205

(usa Ubuntu)

Enviado em 10/03/2020 - 02:40h

perfection escreveu:

Me deparei com um código que eu anotei de algum lugar na Internet para algum tipo de teste que eu estava fazendo que não lembro hoje.
Então apagando os códigos que não usarei, este era um deles, me deparei com uma opção do scanf que não estou conseguindo compreender.
%m 

Lendo no man do scanf ele diz que isso seria um tipo de conversão de alocação dinâmica.
BUM! Então a coisa piorou de vez para mim.

O Pedaço de código que estou analisando é esse:
   char *nome;
scanf("%m[^\n]", &nome); // lê linha, alocando o espaço necessário
printf("Bom dia, %s\n", nome);
printf("Size of de nome = %d\n", sizeof(nome));


Note que “sizeof(nome)” (que poderia ter sido escrito como “sizeof nome”) é um valor constante do programa, e indica o tamanho ocupado pela variável nome para guardar o endereço onde o valor lido reside, não o conteúdo lido para esse espaço. Se você quiser saber o tamanho (mínimo) da área alocada, deveria usar “strlen(nome)+1”.

No momento não tenho obviamente uma aplicação para isso, porém meu objetivo é entender o que é isso já que passou pela minha mão.

DÚVIDAS:
Porque usar o %m ?


Por razões de segurança (evitar erros do tipo buffer overrun ou stack overflow, que é de onde vem o nome do famoso website com dicas sobre programação), você deve sempre limitar o tamanho máximo das strings que você recebe de fontes externas ao programa.

Entretanto, há casos em que você quer ler uma string cujo comprimento máximo você não tem como estimar de antemão. Nesses casos, se você tiver um valor pequeno demais, a operação de leitura pode ficar com um valor truncado. Por outro lado, se você alocar um valor muito grande, pode acabar incorrendo em desperdício (e você pode nunca ter certeza de se o que lhe parece “muito grande” vai realmente acomodar algum dado gigantesco que possa um dia aparecer como entrada).

O modificador m, portanto, é uma alternativa para que as funções da família de scanf() possam ler strings de forma mais flexível, alocando, no momento em que o programa é executado, uma quantidade de memória suficiente para acomodar todos os caracteres que compõem os caracteres recebidos como entrada para tal string e o byte nulo que funciona como marcador do final da string. O funcionamento é tal que a função, internamente, realiza operações de alocação de memória suficiente, transfere os caracteres lidos e o byte nulo para dentro da área alocada, e devolve o endereço do (i.e. um ponteiro para o) primeiro caráter armazenado nessa região de memória. Esse ponteiro pode, posteriormente, quando a string lida não for mais necessária, ser usado para devolver ao sistema a memória que fora alocada para recebê-la.

Para que tipo de situação isso seria necessário?


Para quando você não tem como garantir um limite a quantidade de dados que podem chegar pelo canal de entrada.

Não entendi que tipo de conversão ele está fazendo, imaginando que seja uma conversão.


Não é uma conversão. É um modificador para conversões de dados destinada a leitura de strings (i.e. "%s", "%[…]", "%ls" e "%l[…]"). Você pode pensar nessa opção como uma alternativa à especificação de largura máxima. Por exemplo, enquanto "%31[^\n]" serve para ler uma linha com tamanho máximo de 31 caracteres e mais um possível 32º caráter para o byte nulo, "%m[^\n]" permite ler uma linha com um comprimento tão longo quanto a memória livre do programa puder suportar.

Para que fique bem claro, os dois códigos abaixo são equivalentes, mas o primeiro tende a ser mais eficiente, porque a função sscanf() pode fazer o trabalho todo varrendo a string uma única vez, ao passo que o segundo código tem de varrê-la duas vezes.

const char *big_data_buffer;	// Conteúdo é definido em outra parte do programa.
/* ... */
char *linha;
if(sscanf(big_data_buffer, "%[^\n]", &linha)==1){
// Aqui, “linha” agora aponta para uma cópia da linha que foi extraída de “big_data_buffer”
/* ... usa “linha” ... */
free(linha); // Quando a linha lida não for mais necessária, libera a memória alocada por sscanf().
}
else{
// Não foi possível extrair uma linha de “big_data_buffer”. Se quiser, pode dar um tratamento de erro.
}

const char *big_data_buffer;	// Conteúdo é definido em outra parte do programa.
/* ... */
char *end_of_line=strchrnull(big_data_buffer, '\n'); // Primeira varredura do buffer.
char *linha;
ptr_diff_t length=end_of_line-big_data_buffer;
if(length>0 && (linha=malloc(length+1))){
strncpy(linha, big_data_buffer, length); // Segunda varredura para efetuar a cópia.
linha[length]='\0';
// Aqui, “linha” agora aponta para uma cópia da linha que foi extraída de “big_data_buffer”
/* ... usa “linha” ... */
free(linha); // Quando a linha lida não for mais necessária, libera a memória alocada por malloc().
}
else{
// Não foi possível extrair uma linha de “big_data_buffer”. Se quiser, pode dar um tratamento de erro.
}


Existe um outro caso de uso, que não está ligado a strings, mas a sequências de caracteres que não são strings (porque não contêm o byte nulo como terminador), que são feitas as conversões "%c" e "%lc". Nesse caso, o modificador "m" não é uma alternativa à especificação de tamanho, mas provoca a alocação de memória correspondente tamanho anteriormente especificado. Por exemplo, os dois códigos abaixo são equivalentes.

// Macros para transformar valor inteiro em string (tem de ser em duas etapas).
#define STR(x) XSTR(x)
#define XSTR(x) #x

#define DYN_ARRAY_SIZE

char *dyn_array; // Note que esse array dinâmico não é uma string, pois não necessariamente contém o byte terminador.
int dyn_array_length=0; // Tamanho efetivo pode ser menor que o máximo.
// A expansão da macro, abaixo, produz o equivalente a “scanf("%300mc%n", &dyn_array, &dyn_array_length)”.
if(scanf("%" STR(DYN_ARRAY_SIZE) "mc%n", &dyn_array, &dyn_array_length)==1 && dyn_array_length>0){
/* ... usa os caracteres alocados e recebidos em “dyn_array” ... */
free(dyn_array);
}
else{
/* Trata o erro de leitura e/ou alocação, se quiser. */
}

#define STR(x) XSTR(x)
#define XSTR(x) #x

#define DYN_ARRAY_SIZE

char *dyn_array; // Note que esse array dinâmico não é uma string, pois não necessariamente contém o byte terminador.
int dyn_array_length=0; // Tamanho efetivo pode ser menor que o máximo.
if(
(dyn_array=malloc(DYN_ARRAY_SIZE)) &&
// A expansão da macro, abaixo, produz o equivalente a “scanf("%300c%n", &dyn_array, &dyn_array_length)”.
scanf("%" STR(DYN_ARRAY_SIZE) "c%n", &dyn_array, &dyn_array_length)==1 &&
dyn_array_length>0
){
/* ... usa os caracteres alocados e recebidos em “dyn_array” ... */
free(dyn_array);
}
else{
/* Trata o erro de leitura e/ou alocação, se quiser. */
}


Não entendi porque alocar memória, imaginando que ele esteja fazendo isso.


Porque, para gravar um dado que é lido, precisa haver memória destinada a receber desse dado. Você pode providenciar essa memória de forma fixa (com tamanhos conhecidos desde o momento da compilação), ou pode, no memento da execução, pedir essa memória ao sistema e referenciá-la por meio de ponteiros.

ADICIONAL
Ainda lendo na Internet parece que entendi o seguinte:
char word[32];
scanf("%31s", word); // Entendo que aloquei 31 espaços para a palavra digitada


Não. Você alocou 32 posições ao declarar o array, e, ao ler (no memento da execução), limita a quantidade máxima de caracteres a serem recebidos a 31 (como scanf[i]() sempre grava um [i]byte terminador após a leitura, o tamanho alocado tem de ser maior ou igual a 32).

char *word;
scanf("%ms", &word);
// Eu estaria alocando a Qtd de espaços de acordo com o que foi digitado?
// Exemplo: No caso VIVA eu alocaria 4 Espaços? Seria isso?


Sim para primeira pergunta. Não para a segunda, porque o byte terminador da string entra na contagem de caracteres alocados, de modo que se você digitasse “VIVA”, o tamanho alocado seria maior ou igual a 5 (a função pode alocar mais do que o estritamente necessário para acomodar seus dados; como a operação de alocação e realocação pode ser relativamente custosa, em vez de ir realocando memória a cada caráter lido, a implementação pode ir alocando sucessivos bloquinhos de memória, e só tentar uma alocação nova quando todos os caracteres do bloquinho anterior tiverem sido ocupados).

O QUE GERA AINDA MAIS DÚVIDA
Eu defini para mim, usar o scanf da forma abaixo:
char Texto[15]; scanf("%14[^\n]s", Texto); while(getchar() != '\n'); 


O código acima tem alguns erros, a saber:

  • O caráter "s" após o fechamento dos colchetes na string de formatação de scanf() está sobrando (e, na prática, impede o pleno sucesso da função, a não ser no caso de o usuário digitar 15 ou mais caracteres, e o 15º caráter ser a letra s).

  • Aquele while pode entrar em loop infinito se ocorrer um erro ou fim prematuro do arquivo durante a execução de scanf().

Será então que eu deveria ou seria melhor que eu usa-se ponteiro?
char *Texto; scanf("%m[^\n]s", &Texto); while(getchar() != '\n'); 


Depende de se sua entrada puder ter um tamanho maior do que você pode prever com segurança.

E note que o bloco acima também tem erros.

  • De novo, o caráter "s" após o fechamento dos colchetes está sobrando. Desta vez, no entanto, ele nunca será satisfeito, porque ou a leitura da string terminará com uma quebra de linha, e não com um s, o a função terá tido um erro de leitura, e não chegará ao "s" usado na formatação.

  • Aqui, também, existe a chance de loop infinito no while, se acontecer algum erro de leitura ou fim prematuro de arquivo.


... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)

3. Re: scanf %m não compreendi [RESOLVIDO]

Mr.Perfection
perfection

(usa Slackware)

Enviado em 10/03/2020 - 13:54h

paulo1205 escreveu:
Defini para mim, usar o scanf da forma abaixo:
char Texto[15]; scanf("%14[^\n]s", Texto); while(getchar() != '\n'); 

O código acima tem alguns erros, a saber:
O caráter "s" após o fechamento dos colchetes na string de formatação de scanf() está sobrando (e, na prática, impede o pleno sucesso da função, a não ser no caso de o usuário digitar 15 ou mais caracteres, e o 15º caráter ser a letra s).

Paulo obrigado por todas as informações!
Não sei onde aprendi a colocar o "s" após os colchetes. Ou mesmo se isso foi uma invenção minha por desatenção!

Então gostaria de saber qual a forma correta que eu devo usar:
A) scanf("%14s[^\n]", Texto);
B) scanf("%14[^\n]", Texto); // SEM O S

Penso que o correto seja a Letra A, visto que o S é para informar que é uma cadeia de caracteres.



4. Re: scanf %m não compreendi

Paulo
paulo1205

(usa Ubuntu)

Enviado em 11/03/2020 - 23:43h

perfection escreveu:

Paulo obrigado por todas as informações!
Não sei onde aprendi a colocar o "s" após os colchetes. Ou mesmo se isso foi uma invenção minha por desatenção!

Então gostaria de saber qual a forma correta que eu devo usar:
A) scanf("%14s[^\n]", Texto);
B) scanf("%14[^\n]", Texto); // SEM O S

Penso que o correto seja a Letra A, visto que o S é para informar que é uma cadeia de caracteres.


Não. A forma (B) é correta.

O que você escreveu na forma (A) significa literalmente o seguinte: “ignore zero ou mais espaços em branco†, leia (no mínimo 1 e) no máximo 14 caracteres diferentes de espaços em branco, armazenado tais caracteres em posições sucessivas de memória a partir do endereço indicado pelo valor de Texto, e, depois disso, procure pelo caráter '[', seguido por zero ou mais espaços em branco, e depois procure pelo caráter ']'”, Por outro lado, a forma (B) tem o seguinte sentido: “começando imediatamente (i.e. sem ignorar espaços em branco), leia (no mínimo 1 e) no máximo 14 caracteres que não pertencem ao conjunto de caracteres que contém (apenas) o elemento '\n', armazenando tais caracteres em posições sucessivas de memória a partir do endereço indicado pelo valor de Texto”.

A conversão "%s" é uma coisa, e a conversão "%[" é outra coisa, completamente distinta e com regras distintas. Leia atentamente a documentação.

----
† “Espaço em branco”, para a família de scanf(), inclui o espaço propriamente dito, a tabulação horizontal ('\t'), a mudança de linha ('\n'), o retorno do carro ('\r'), o avanço de página ('\f') e a tabulação vertical ('\v'). Dependendo da locale ativa, outros caracteres também podem ser considerados espaços em branco (por exemplo, com en_US.ISO8859-1, é possível que o caráter '\xA0', que é o espaço em branco sem quebra de linha, seja considerado um espaço em branco por isspace() e, conseuqntemente, por scanf().

... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts