Logo Hardware.com.br
andremachado
andremachado Highlander Registrado
3.3K Mensagens 2K Curtidas

Função para validar parâmetro numérico em C

#1 Por andremachado 17/02/2014 - 11:18
Estou criando uma função em C para validar um parâmetro passado por linha de comando (mais precisamente, por optarg do getopt). A ideia é que a função verifique se o parâmetro é um número. Eu fiz o seguinte:


int is_valid_number(char str[])
{
int cnt, resp;

#define SIZE sizeof( str ) / sizeof( char )

resp = 0;

for (cnt = 0; cnt < SIZE; cnt++)
{
if ( isdigit(str[cnt]) )
{
printf("%d %c e digito\n", cnt, str[cnt]);
resp = 1;
}
else
{
printf("%d %c nao e digito\n", cnt, str[cnt]);
return(0);
}
}
printf("%d %s\n", resp, str);
return(resp);
}


O problema é que, ao executar com qualquer parâmetro, digamos 122, o resultado obtido é esse:


0 1 e digito
1 2 e digito
2 2 e digito
3 nao e digito


Aparentemente, ele está pegando o terminador \0 e o interpretando. O que eu poderia fazer para que isso não ocorresse?

Grato.
ripongao
ripongao Veterano Registrado
755 Mensagens 94 Curtidas
#4 Por ripongao
17/02/2014 - 17:17
aquela assertiva esta correta? a do SIZE? pelo que sei pode ser usado para dados estáticos não? e como os dados estão sendo inseridos de forma dinâmica aí acho que pode ser o motivo do erro, sei lá, tentaria strlen() lá só para testar.

A linha de comandos geralmente é terminada por um CR ou CRLF (0x0a,0x0d), apenas depois é que vem o 0x00. Bom, ao menos é o que vejo no linux e windows, daí achar que o erro tá na declaração #define. Não emitiu nenhuma aviso na hora de compilar!?

Bom, se falei besteira apenas ignore, a intenção é ajudar.
Desliguei-me do fórum. Conta canelada.
pflynn
pflynn Ubbergeek Registrado
5.5K Mensagens 122 Curtidas
#5 Por pflynn
20/02/2014 - 15:51

Aparentemente, ele está pegando o terminador \0 e o interpretando. O que eu poderia fazer para que isso não ocorresse?


Você está correto. E esta é na verdade a grande sacada. O caractere nulo é o caractere utilizado como indicador do final de uma string na linguagem C. Isso significa que para analisar uma string do começo ao fim, você não precisa saber o seu tamanho. Basta percorre-la até encontrar o caractere nulo. Quando você encontra o caractere nulo, você sabe que chegou ao fim da string. No caso da sua função, você percorre a string do começo até o final. Se em algum momento antes do final você encontrar um caractere que não seja um dígito numérico, então a string não é um número válido (de acordo com a sua regra). Se você chegar ao final da string sem encontrar um caractere que não é um digito numérico, então ela é um numero válido. Sua função poderia ser assim:

[PHP]int is_valid_number(char str[]){ while(*str) if(!isdigit(*str++)) return 0; return 1;}[/PHP]

nesse caso, o valor de retorno é 0 se a string for um número inválido, e 1, caso ela represente um número válido.
------------------------------------------------
Muito bom. Mas tijolo não revida!
------------------------------------------------
ripongao
ripongao Veterano Registrado
755 Mensagens 94 Curtidas
#6 Por ripongao
20/02/2014 - 18:15
mas e se houverem espaços ou aspas no meio dos parâmetros, então a questão muda de figura né!? Pois vai retornar número inválido e mesmo assim existem números válidos após o inválido. Mas aí checando o argv/argc daria pra sanar esse empecilho.

Sendo franco, eu faria no dedo ao invés de chamar funções, digo, sempre fiz no dedo esse tipo de checagem. Compararia se o caracter esta entre 0 e 9 e o validaria-o, caso encontrasse algum 0x0d,0x0a,0x09,0x20,..., ignoraria e se encontrasse um 0x00 terminaria, mas com argv/argc fica bem mais fácil.

ripongao disse:

A linha de comandos geralmente é terminada por um CR ou CRLF (0x0a,0x0d), apenas depois é que vem o 0x00. Bom, ao menos é o que vejo no linux e windows, daí achar que o erro tá na declaração #define. Não emitiu nenhuma aviso na hora de compilar!?

Fiz um teste aqui agora no windows e ele remove o CRLF da linha de comandos, a terminação é 0x00. Então, havia falado besteira mesmo, agora confirmei.

Uma dúvida:
O #define que citei acima, esta errado?!? Em C é possível fazer aquilo de forma dinâmica ao invés de estática?
Desliguei-me do fórum. Conta canelada.
intruso
intruso Tô em todas Registrado
1.8K Mensagens 41 Curtidas
#7 Por intruso
20/02/2014 - 18:38
ripongao disse:
mas e se houverem espaços ou aspas no meio dos parâmetros, então a questão muda de figura né!? Pois vai retornar número inválido e mesmo assim existem números válidos após o inválido. Mas aí checando o argv/argc daria pra sanar esse empecilho.



Um número não tem espaços no meio, se tiver, é mais de um número. Mas, ai entramos na filosofia, porque depende da definição do que é uma entrada válida no programa e até mesmo de como seria a entrada do programa, pelo que entendi da pergunta, não importa. Porque ele está testando se uma string é um número ou não, não está analisando o formato de entrada da string para verificar se existem números ou não, são perguntas diferentes.

Eu, particularmente, gostei muito da abordagem do pflynn.

Inclusive porque o caractere especial pode mudar de SO e também possibilita incluir testes para caracteres que realmente podem representar um número mais adiante, durante a evolução de um programa, com a virgula ou o ponto, ou mesmo o teste para verificar se é um número negativo.

Abs.
pflynn
pflynn Ubbergeek Registrado
5.5K Mensagens 122 Curtidas
#9 Por pflynn
20/02/2014 - 19:41
ripongao disse:
Realmente o exemplo do espaço e aspas que eu disse foi infeliz, eu não consegui me expressar, e como não trampo com C prefiro nem alongar, fora da minha alçada.


Você tem razão quando questiona a macro:

ripongao disse:

Uma dúvida:
O #define que citei acima, esta errado?!? Em C é possível fazer aquilo de forma dinâmica ao invés de estática?


A macro

[php]#define SIZE sizeof( str ) / sizeof( char )[/php]não retorna o tamanho da string como pode parecer. sizeof é em um operador especial. É um operador em tempo de compilação. Isso significa que sizeof retorna o valor da operação em tempo de compilação. No caso:

[php]sizeof(str)[/php]retorna o valor do tamanho de um ponteiro para char se str for um char[], como foi o caso. Esse valor é dependente de plataforma. Num sistema de 64 bits, sizeof(char[]) retorna 8 (8 bytes = 64 bits = tamanho de um ponteiro). Podemos dizer que o loop que avalia o tamanho da string funcionou "por sorte". Por sorte porque os argumentos testados foram todos strings de tamanho menor do que 8 bytes (122, por exemplo, que tem quatro bytes - incluindo o terminador nulo) - assumindo, claro, que os testes foram feitos num sistema de 64 bits. Se o teste for feito com uma string de tamanho maior do que 8 bytes, o comportamento do programa é indefinido. O bug, em questão, é um bug de "estouro de buffer" (o famoso buffer overflow).

[EDIT] Na verdade não é um bug de buffer overflow, como eu disse incorretamente. O problema é que o programa não vai avaliar mais do que sizeof(char[]) caracteres numa string. Em 64 bits, isso significa que a função ficou limitada a analisar, no máximo, 8 caracteres.
------------------------------------------------
Muito bom. Mas tijolo não revida!
------------------------------------------------
ripongao
ripongao Veterano Registrado
755 Mensagens 94 Curtidas
#10 Por ripongao
20/02/2014 - 21:29
sim, essa era a idéia que eu tinha em mente, se fosse windows/linux 32 bits seriam 4 bytes, e caso ms-dos seria 16 bits, afinal, é a primeira coisa que muda ao evoluir a arquitetura, depois virão 128, 256,... . Aí haveria o estouro ou até mesmo a inserção de um número válido porém truncado, no caso gerando um outro número ao invés do entrado pelo usuário.

Sobre o anterior, a idéia que quis passar era a de testar via linha de comandos versus pedir a string internamente no programa através de um scanf() por exemplo (nota: não sei se scanf deixa o 'enter' no buffer).
Pela linha de comandos, tanto no windows/linux/ms-dos o enter digitado pelo teclado é removido ao chamar a função nativa de cada S.O., assim sendo a variável irá armazenar apenas o número seguido de zero ou null ou terminador da string melhor falando, lembrando que no ms-dos a terminação de string é um cifrão para funções nativas, mas devido ao tópico ser sobre C, então optei por não comentar isso, sendo que em C toda string é terminada em nulo.
O que supus foi que o usuário testou via linha de comandos para após inserir ou criar uma biblioteca com esta função, e aí pode acontecer o problema do 'enter' aparecer no restante do número, no caso, ao fim do número quando armazenado em uma variável/buffer qualquer, retornando um 'número não válido' através da função chamada caso inteiro com sinal, ou 'nan' caso ponto flutuante.

Como não uso geralmente funções para tal, desconheço todas as funções que lidam com símbolos numéricos citadas, talvez as mesmas façam verificação sobre caracteres a mais ao fim do número e a removam, daí eu não me aventurar a responder, apesar de poder fazer uns testes chamando as funções em qualquer linguagem, prestando apenas atenção na convenção de chamadas.
Desliguei-me do fórum. Conta canelada.
Henry-Keys
Henry-Keys Geek Registrado
1.8K Mensagens 235 Curtidas
#13 Por Henry-Keys
22/02/2014 - 08:49
Fergo disse:
Ainda não entendi porque não utiliza a strtol(), sendo que ela faz tudo isso pra você.

O problema é que strtol não leva em conta os caractere não numéricos, ou seja, enviar uma string assim 1672aabnsj retorna 1672 (quando não deveria retornar coisa alguma), e não podemos efectuar uma validação numérica deste jeito.
http://www.cplusplus.com/reference/cstdlib/strtol/

Abraços.
C#_Compiler
C#_Compiler Novo Membro Registrado
12 Mensagens 0 Curtidas
#14 Por C#_Compiler
02/03/2014 - 19:48
int is_valid_number(char str[])
{
int cnt, resp;

#define SIZE sizeof( str ) / sizeof( char )

resp = 0;

for (cnt = 0; cnt < SIZE; cnt++)
{
if ( isdigit(str[cnt]) )
{
printf("%d %c e digito\n", cnt, str[cnt]);
resp = 1;
}
else if( (strcmp(str[cnt], '\0')) ) //Só imprime o resultado negativo se o dado não for um espaço
{
printf("%d %c nao e digito\n", cnt, str[cnt]);
return(0);
}
}
printf("%d %s\n", resp, str);
return(resp);
}
Adicionei um condicional que não imprime o resultado negativo caso o dado seja '\0'. Não testei o código, espero que ajude.
ripongao
ripongao Veterano Registrado
755 Mensagens 94 Curtidas
#15 Por ripongao
03/03/2014 - 16:27
mas ô C#, comentei isso, o #define é estático em tempo de compilação e o número/tamanho de parâmetros dinâmico em tempo de execução do programa.
Olhar resultado negativo é olhar o bit mais à esquerda de um número (complemento de 2) , se for 1 é negativo, se for 0 é positivo pois estamos lidando com 'int' (32 bits). Se fosse sem sinal aí qualquer valor é um valor válido.
Por ser 32 bits com sinal, peço para entrar um número maior que o bit mais à esquerda setado em 32 bits, algo como 2147486648 ou maior.

modo capitão nascimento on
tá querendo sacanear a gente 01, peraí, segura essa granada se dormir a gente vai pro brejo, tacando buffer overflow ué
modo capitão nascimento off
Desliguei-me do fórum. Conta canelada.
© 1999-2024 Hardware.com.br. Todos os direitos reservados.
Imagem do Modal