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)