Duvida com realloc , em C

1. Duvida com realloc , em C

Jean
JeanYamada

(usa Outra)

Enviado em 07/11/2014 - 23:35h

Galera sou novo aqui, gostaria de mostrar um código que implementei,oque acontece é o seguinte estou usando realloc para criar uma string,quando digito 100 caracteres ou mais,e depois com backspace deleto os caracteres um por um assim acaba ocorrendo um erro, se alguém puder ajudar resolver este erro,Obrigado.

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>



int getch(void)
{
struct termios oldattr, newattr;
int ch;
tcgetattr( STDIN_FILENO, &oldattr );
newattr = oldattr;
newattr.c_lflag &= ~( ICANON | ECHO );
tcsetattr( STDIN_FILENO, TCSANOW, &newattr );
ch = getchar();
tcsetattr( STDIN_FILENO, TCSANOW, &oldattr );
return ch;
}


int main()
{
char *palavra=NULL;
char *aux=NULL;
int tamanho=0;
char tecla;

do{
if(palavra==NULL)
palavra=(char*)malloc(sizeof(char));
if(palavra==NULL){
printf("Erro na alocacao!");
exit(0);
}
system("clear");
printf("Palavra : %s tamanho: %d",palavra,tamanho);
fflush(stdin);
tecla=getch();
if(tecla!=10 && tecla!=127){
palavra[tamanho]=tecla;
tamanho++;
aux=realloc(palavra,(tamanho+1)*(sizeof(char)));
palavra=aux;
palavra[tamanho]='{TTEXTO}';
}
if(tecla==127){
if(tamanho>0){
tamanho--;
if(tamanho==0){
free(palavra);
palavra=NULL;
}
else{
aux=realloc(palavra,tamanho*sizeof(char));
palavra=aux;
}
if(palavra!=NULL)
palavra[tamanho]='{TTEXTO}';
}
}
}while(tecla!=10);

return 0;
free(palavra);
palavra=NULL;
}





  


2. Re: Duvida com realloc , em C

Jean
JeanYamada

(usa Outra)

Enviado em 08/11/2014 - 13:50h

Este '{TTEXTO}' é um '\ 0' .


3. Re: Duvida com realloc , em C

Paulo
paulo1205

(usa Ubuntu)

Enviado em 08/11/2014 - 22:42h

JeanYamada escreveu:

Galera sou novo aqui, gostaria de mostrar um código que implementei,oque acontece é o seguinte estou usando realloc para criar uma string,quando digito 100 caracteres ou mais,e depois com backspace deleto os caracteres um por um assim acaba ocorrendo um erro, se alguém puder ajudar resolver este erro,Obrigado.


Seu erro principal -- isto é: o que faz o programa tomar um SIGSEGV e capotar -- é o realloc() que você faz na hora de apagar o caráter. Pela forma como você trabalha com a variável tamanho ao longo do programa, também essa invocação a realloc() deveria ser feita com tamanho+1, como as outras chamadas a realloc() e malloc().

Mas, se me permite, gostaria de ir além no seu código, dando-lhe outras dicas.

A primeira é meramente estética, e mais para uso aqui neste fórum. Os scripts em PHP do site do VoL interceptam a sequência “\0” e a substituem com strings estranhos como “{TEXTO}”, “{TEXTO2}” etc. Você pode evitar isso simplesmente trocando ocorrências de “'\0'” por “0”. Pode, inclusive, fazer isso nos seus próprios programas, não apenas no fórum. Como char é um tipo aritmético inteiro no C e no C++, o valor numérico de um byte nulo ou de um inteiro nulo é garantidamente idêntico, e a conversão automática ocorre sem problemas tanto em atribuições como em comparações ou outras expressões. Você só não pode confundir o byte nulo, que tem o valor inteiro zero, com o caráter que representa o algarismo zero na hora de imprimir textos (esse caráter tem, no ASCII, o valor inteiro 48).

De volta ao seu código, um erro que é crasso, embora seu efeito não se faça sentir, está ao final do programa: os dois comandos após o “return 0” estão obviamente no lugar errado.

Um problema mais sutil mas potencialmente mais sério é a mistura de operações no descritor STDIN_FILENO com leituras no stream stdin. Por mais que você use truques sujos como fflush(stdin) -- que é uma excrescência que eu recomendo que você remova e não use nunca mais --, não dá garantias _reais_ de que o buffer de entrada vai estar totalmente vazio no momento da próxima leitura. Como você está fazendo manipulações de baixo nível no terminal para desligar o eco e o modo canônico, deveria também fazer a leitura dos dados de entrada em baixo nível, usando a chamada read(). Abaixo eu mostro como.

No seu laço de repetição, quando a palavra fica vazia, você faz uma alocação inicial de espaço para a palavra mas não inicializa o conteúdo apontado por palavra com um string vazio. Mesmo assim, chama printf() para imprimir tal conteúdo. Isso é errado, podendo provocar desde a impressão de alguns lixos (como aconteceu comigo enquanto estava testando seu código) até outros efeitos imprevisíveis, dependendo do quanto o programa avance na memória tentando localizar o byte terminador que não estava onde deveria estar.

O uso de constantes inteiras no programa, especialmente para fazer o papel de caracteres, é um coisa que você deveria evitar. Uma delas, 127, que provavelmente você associa à tecla Backspace é uma coisa que você definiu arbitrariamente, que geralmente vale no Xterm e seus substitutos do KDE e GNOME, mas que pode não ser verdadeira no console de texto ou outros terminais em outros sistemas (outro valor muito comum para essa função é 8, que é o que o ASCII define como “back space”; 127, por sua vez, corresponde a “delete”). Para a marca de fim de linha, seria melhor você usar a constante literal de caráter “'\n'”. Para o caráter de apagamento, o mais correto seria você consultar a configuração do terminal. De novo, veja abaixo.

O nome getch() é usado pela biblioteca Curses para fazer algo muito parecido com o que você está fazendo. Para evitar conflitos de nomes, especialmente porque seu programa faz uso de recursos relacionados ao terminal (e o que vou mostrar, mais ainda) é, interessante mudar o nome dessa função. Eu usei a prosaica solução de chamá-la my_getch().

Existe uma situação de erro que você não previu no seu programa, que é o fim prematuro da entrada. Seria bom prevê-lo, e é motivo pelo qual a função my_getch() deve retornar um valor do tipo int, e a variável que recebe esse valor também ser desse mesmo tipo. Também o bloco de tratamento de valor recebido tem de ser alterado, para tratar a indicação de erro.

Por convenção, um programa que execute com sucesso devolve o código de retorno zero, e qualquer outro valor é interpretado ou como erro ou como caso particular, que merece tratamento especial.

Se você ler a documentação de malloc(), free() e realloc(), verá que esta última pode substituir as outras duas, se receber valores especiais como parâmetros. Com isso, é possível simplificar o programa e ter apenas uma chamada a realloc() dentro do laço de repetição, em lugar de uma a malloc() e duas a realloc(). Uma rearrumação no bloco de testes da tecla lida facilita essa construção.

Chamar system() somente para limpar a tela é um desperdício de tempo e recursos. No seu programa, pode não ser um absurdo, mas veja o que acontece como resultado de um simples “system("clear")”:

1) A função system() cria um processo novo e para, esperando esse processo terminar.
2) O processo novo executa o shell (“/bin/sh”), passando a ele o comando “clear”, para que ele decida o que fazer.
3) Após processar o comando, o shell decide que não é um comando interno, então procura por ele nos diretórios que constam na variável de ambiente PATH.
4) Admitindo que o shell encontrou o programa “clear” (por exemplo, em /usr/bin/clear), o shell cria outro processo, e para, esperando esse processo terminar.
5) O novo processo criado executa o programa encontrado.
6) O comando clear, internamente, vê qual o tipo do seu terminal e lê de uma base de dados correspondente a tal tipo a sequência de bytes que deve ser enviada para limpar a tela, e a envia.
7) O processo em que o clear executou termina.
8) O shell reconhece o fim do processo que criou, e pega o código de terminação.
9) O processo do shell termina, enviando o mesmo código de terminação que colheu.
10) Dentro da função system(), percebe-se o fim do processo filho. Pega-se o código de terminação.
11) A função retorna para o ponto onde foi chamada, entregando como código de retorno um valor compatível com o fluxo de execução acima (se tudo correu bem, provavelmente esse valor será zero).
12) Seu programa tem de tratar o valor devolvido por system(), para saber se conseguiu o resultado esperado ou não.

Viu só? Tudo esse trabalho só para conseguir a funcionalidade do passo 6, que poderia ser trazida para dentro do seu programa com muito pouco esforço (ver abaixo). Note quantos dos passos acima são passíveis de falha, num caso geral, com comandos provavelmente mais complexos do que um mero “clear” (ainda que alguns desses casos sejam muito difíceis de ocorrer):

(1) e (4) podem não conseguir criar um novo processo;
(3) pode estar sujeito a manipulações indevidas do valor da variável de ambiente PATH, que pode fazer com que o programa não seja encontrado ou que encontre alguma versão indevida de clear (o que é inclusive um problema de segurança);
(2) e (5) podem falhar por causa dos valores de outras variáveis de ambiente ou condições inesperadas, como falta de permissão em arquivos;
(6) pode ter problema se o próprio programa final executar com erro (inclusive o próprio “clear”: mesmo sendo simples, o tipo de terminal pode não existir, ou a base de dados de terminais pode não ser encontrada, ou o usuário pode informar o tipo de terminal errado na variável de ambiente TERM);
(8) e (9) alguma anomalia (por exemplo: esgotamento de memória do sistema operacional) ou intervenção manual podem forçar a terminação do shell antes da terminação do seu filho, que pode afetar o código de terminação recebido em (10);
(11) e (12) em alguns casos, a aplicação final e o shell usado para chamá-la podem devolver um código de retorno que entra em conflito com o código de retorno usado internamente pela função system() para indicar que não conseguiu criar o processo ou executar o shell;
(12) muitas aplicações nem mesmo testam o valor de retorno da execução de system().

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <curses.h>
#include <term.h>


#define DEFAULT_ERASE 127


int my_getch(void){
struct termios oldattr, newattr;
unsigned char ch; /* Note que eu mudei para _unsigned_ char. */
int retcode;
tcgetattr(STDIN_FILENO, &oldattr);
newattr=oldattr;
newattr.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newattr);
retcode=read(STDIN_FILENO, &ch, 1);
tcsetattr(STDIN_FILENO, TCSANOW, &oldattr);
return retcode<=0? EOF: (int)ch;
}

int main(void){
char *palavra=NULL;
char *aux=NULL;
int tamanho=0;
int tecla;
int can_clear_screen=1;
int erase_char=DEFAULT_ERASE;
struct termios termattr;

/* Ajusta o terminal para, abaixo, limpar a tela sem chamar system(). */
if(setupterm(NULL, STDOUT_FILENO, NULL)==ERR)
can_clear_screen=0; /* Nao conseguiu inicializar terminal. */

if(
tcgetattr(STDIN_FILENO, &termattr)==0 &&
termattr.c_cc[VERASE]!=_POSIX_VDISABLE
)
erase_char=termattr.c_cc[VERASE];

do {
aux=realloc(palavra, tamanho+1);
if(aux==NULL){
fprintf(stderr, "Erro de alocação!\n");
exit(1); /* Zero normalmente indica sucesso. Esta é uma falha. */
}
palavra=aux;
palavra[tamanho]=0;

if(can_clear_screen)
putp(clear_screen); /* Melhor que “system("clear")”. */

printf("Palavra (%d caracteres): %s\n", tamanho, palavra);

tecla=my_getch();
if(tecla==EOF){
fprintf(stderr, "Fim prematuro de dados de entrada.\n");
exit(1); /* Aqui também indicamos uma falha. */
}
if(tecla==erase_char){
if(tamanho>0){
tamanho--;
palavra[tamanho]=0;
}
}
else if(isprint(tecla)){
palavra[tamanho]=tecla;
tamanho++;
/*
ATENÇÂO: Neste ponto do programa, está faltando o terminador
nulo no final do string. No entanto, eu sei que a próxima
iteração vai reservar espaço para ele e colocá-lo lá ANTES
de tentar operar com o string.
*/
}
} while(tecla!='\n');

free(palavra);
palavra=NULL;

return 0;
}


Como melhorar o código acima? Fazendo com que a aplicação use realmente os recursos da biblioteca Curses, em vez de chamadas de baixo nível da biblioteca terminfo, além de tcgetattr() e my_getch().






Patrocínio

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

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts