Jump to content

Recommended Posts

Posted

Olá ppl;

Depois de uns dias para preparar um exame de SO estou de volta para continuar o meu calvário de C 😛

Estou a seguir o manual online que o Bscara me aconselhou e surgiu-me aqui uma dúvida, tenho de fazer o seguinte:

Faça um programa que leia quatro palavras pelo teclado, e armazene cada palavra em uma string. Depois, concatene todas as strings lidas numa única string. Por fim apresente esta como resultado ao final do programa.

Fiz assim:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
   int tam;
   char p1[40], p2 [40], p3[40], p4[40], pfinal[40];
   printf("Introduza uma palavra!\n");
   gets(p1);
   printf("Introduza uma palavra!\n");
   gets(p2);
   printf("Introduza uma palavra!\n");
   gets(p3);
   printf("Introduza uma palavra!\n");
   gets(p4);
   strcat(p2, p1);
   strcat(p3, p2);
   strcat(p4, p3);
   strcat(pfinal, p4);
   tam= strlen(pfinal);
   printf("\n%s e tem o tamanho de %d bytes", pfinal, tam);
   return 0;
}
Funcionar funciona mas antes da string final aparecem-me uns carateres estarnhos, é normal?

Obrigado

gmc11

 

Posted (edited)

é ... tu não inicializas a string pfinal, logo é de supor que tem lixo .. e como nunca atribuis nada a ela .. é esse lixo que aparece

é boa prática inicializar todas as variáveis antes de começar a trabalhar com elas

no caso dos arrays tens (isto para todos os arrays):

memset(p1, 0, 40);

ps : não uses o gets ...

altera esse tipo de chamada por

fgets(p1, 40, stdin);
Edited by HappyHippyHippo
IRC : sim, é algo que ainda existe >> #p@p
Posted (edited)

Pois, mas o problema é que manda usar o gets

A função gets() lê uma string do teclado. Sua forma geral é:

gets (nome_da_string)

As strings que escrevi aparecem todas juntas, só que antes aparecem esses carateres estranhos, vou modificar como disseste..

Ficou assim

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
   int tam;
   char p1[40], p2 [40], p3[40], p4[40], pfinal[40];
   memset(p1, 0, 40);
   memset(p2, 0, 40);
   memset(p3, 0, 40);
   memset(p4, 0, 40);
   memset(pfinal, 0, 40);
   printf("Introduza uma palavra!\n");
   fgets(p1,40, stdin);
   printf("Introduza uma palavra!\n");
   fgets(p2,40, stdin);
   printf("Introduza uma palavra!\n");
   fgets(p3,40, stdin);
   printf("Introduza uma palavra!\n");
   fgets(p4,40, stdin);
   strcat(p2, p1);
   strcat(p3, p2);
   strcat(p4, p3);
   strcat(pfinal, p4);
   tam= strlen(pfinal);
   printf("\n%s e tem o tamanho de %d bytes", pfinal, tam);
   return 0;
}

Mas agora cada uma das strings vem numa linha e o objetivo era concatenar (pelos vistos estão porque está a ler a variável pfinal), certo?

Obrigado

Edited by alphasil
gmc11

 

Posted

só uma nota : tem cuidado porque a função strcat supõe que o primeiro argumento tem tamanho suficiente para guardar o resultado

eu nunca tentei fazer uma concatenação para dar erro por isso não sei o resultado que pode dar,

mas podes sempre verificar se o resultado da função é válido

if (strcat(p1, p2) != p1) {
 // erro !!!
}
IRC : sim, é algo que ainda existe >> #p@p
Posted

mas podes sempre verificar se o resultado da função é válido

if (strcat(p1, p2) != p1) {
 // erro !!!
}

Não, não pode. A função strcat retorna sempre o primeiro parâmetro, independentemente do que a função fizer. A má utilização desta função é uma das causas dos famosos 'buffer overflow'. Dependendo da alocação da memória apontada pelo primeiro parâmetro pode alterar outros blocos de memória alocada dinamicamente, alterar o valor de variáveis locais à função ou corromper a pilha do CPU, levando a estouros na saída da função. Deve-se ter extremo cuidado com a utilização desta e de outras funções como a strcpy pois erros na sua utilização causam bugs muito difíceis de localizar.

Posted (edited)

Não, não pode. A função strcat retorna sempre o primeiro parâmetro, independentemente do que a função fizer. A má utilização desta função é uma das causas dos famosos 'buffer overflow'. Dependendo da alocação da memória apontada pelo primeiro parâmetro pode alterar outros blocos de memória alocada dinamicamente, alterar o valor de variáveis locais à função ou corromper a pilha do CPU, levando a estouros na saída da função. Deve-se ter extremo cuidado com a utilização desta e de outras funções como a strcpy pois erros na sua utilização causam bugs muito difíceis de localizar.

desculpa ... é o que dá responder em cima dos joelhos

Edited by HappyHippyHippo
IRC : sim, é algo que ainda existe >> #p@p
Posted

Pois, mas o problema é que manda usar o gets

A função gets() lê uma string do teclado. Sua forma geral é:

gets (nome_da_string)

As strings que escrevi aparecem todas juntas, só que antes aparecem esses carateres estranhos, vou modificar como disseste..

Ficou assim

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int tam;
char p1[40], p2 [40], p3[40], p4[40], pfinal[40];
memset(p1, 0, 40);
memset(p2, 0, 40);
memset(p3, 0, 40);
memset(p4, 0, 40);
memset(pfinal, 0, 40);
printf("Introduza uma palavra!\n");
fgets(p1,40, stdin);
printf("Introduza uma palavra!\n");
fgets(p2,40, stdin);
printf("Introduza uma palavra!\n");
fgets(p3,40, stdin);
printf("Introduza uma palavra!\n");
fgets(p4,40, stdin);
strcat(p2, p1);
strcat(p3, p2);
strcat(p4, p3);
strcat(pfinal, p4);
tam= strlen(pfinal);
printf("\n%s e tem o tamanho de %d bytes", pfinal, tam);
return 0;
}

Mas agora cada uma das strings vem numa linha e o objetivo era concatenar (pelos vistos estão porque está a ler a variável pfinal), certo?

Obrigado

O que acontece é que o fgets, coloca o '\n' no final de cada string, tens que os retirar.

Posted (edited)

Boas, existem vários pontos que quero abordar nesta questão, nomeadamente más praticas,

e erros, contudo acho importante responder a questão primeiro e só depois prosseguir com os

pontos seguintes.

Primeiro o Erro!

strcat(pfinal, p4); 

Vamos analisar a função passo a passo.

//prototipo da função
char *strcat(char *restrict s1, const char *restrict s2);

segundo a sua descrição no standard (numa tradução solta),

strcat ira concatenar a string apontada por s2,

à string apontada por s1 ( incluindo o "terminador" null byte ou seja '\0' ).

O byte inicial da string s2 ira apagar o terminador do final da s1 escrevendo na posição

do mesmo o byte inicial apontado por s2.

Se a copia ocorrer entre os objetos que se sobrepõem, o comportamento é indefinido.

O que isto significa é que antes de concatenar a p4 a pfinal, a função ira fazer uma "pesquisa"

pelo terminador, e só nessa altura ira copiar p4.

do mesmo. Uma vez que pfinal não foi inicializado, a localização/existência do terminador é indeterminada.

Portanto os caracteres "estranhos" que aparecem antes da string concatenada são o resultado de, "lixo" existente

antes de um terminador.

Agora os outros pontos que gostaria de abordar.

Não sei se reparaste mas o teu programa, iria resultar em frases ao contrario.

imaginemos o seguinte input,

Nós

Estamos

Em

Junho

fazendo o mapa do input para as variáveis,

p1 = Nós

p2 = Estamos

p3 = Em

p4 = Junho

agora vamos ver as concatenações:


strcat(p2, p1);
// ou seja p2 agora é igual a -> Estamos Nós
strcat(p3, p2);
//ou seja p3 agora é igual a -> Em Estamos Nós
strcat(p4, p3);
//ou seja p4 agora é igual a -> Junho Em Estamos Nós

//finalmente o pfinal
strcat(pfinal, p4);
//ou seja pfinal agora é igual a -> lixo existente até um terminador + Junho Em Estamos Nós

/*

Presumo que o objectivo do programa seria Construir uma string concatenada com base no input
mas coerente.
*/

Próximo ponto, o HappyHippyHippo parece estar muito disponível para ajudar, contudo, na minha visão

dizer apenas troca X por Z porque é alegadamente mais segura, não é solução para "ajudar" alguém à aprender.

Por isso, descrevo aqui o problema de gets,

gets, para começar é uma função que foi removida no ultimo standard, por isso devo desde já

dizer que é bom conhecer a sua existência, mas o seu uso deve ser limitado para fins académicos,

como sempre o melhor é analisar a função em maior detalhe.

char *gets(char *s);

Uma tradução "livre" do standard seria:

gets () é uma função que lê os bytes da entrada stdin , para a array apontada por s, até que o carácter '\n' seja lido ou uma condição de fim-de-arquivo 'EOF' seja encontrada. Qualquer (nova_linha '\n') deve ser descartada e um (byte nulo '\0') deve ser colocado imediatamente após o último byte lido na array.

Como é possível observar não existe nenhuma menção em tamanho desta leitura. Contudo visto que em C as arrays não são "naturalmente"

dinâmicas, ou seja não "crescem" conforme necessário automaticamente os programadores têm de ter cuidado para não ler mais dados

do que o espaço que têm disponível, visto que se o fizerem o comportamento do programa seria indefinido.

Ora como o programador não têm controlo sobre o input a ser feito no programa, não pode precaver e reservar espaço suficiente.

Aqui entra a função fgets ,

esta função, permite fazer uma leitura "limitada" por tamanho invés de uma leitura por localização de uma nova linha ou terminador ou EOF

vejamos o prototipo

char *fgets(char *restrict s, int n, FILE *restrict stream);

sem entrar em muitos detalhes, a função ira ler n bytes do stream, e armazena-los em s

Sendo assim, é garantido não ultrapassar a leitura de n bytes, o que permite então com que o programador

conte com o tamanho máximo suportado por s;

Outros Erros, Agora em respostas fornecidas.

fgets não adiciona o carácter identificador de nova linha , fgets limita-se a fazer uma leitura de n bytes

que por consequência pode ser maior do que o input fornecido. O input via stdin por norma esta associado ao

teclado, o que significa que, quando o input foi introduzido, "Enter" foi pressionado, para que este fosse aceite.

Logo sendo este "Enter" à adicionar o carácter identificador de nova linha.

nota: fgets adiciona um terminador '\0' no final da leitura

Edited by dardevelin
Posted

Próximo ponto, o HappyHippyHippo parece estar muito disponível para ajudar, contudo, na minha visão

dizer apenas troca X por Z porque é alegadamente mais segura, não é solução para "ajudar" alguém à aprender.

sabes quantas vezes já foi descrito aqui no forum o porque do uso do fgets em vez do gets ?

fgets não adiciona o carácter identificador de nova linha ... Logo sendo este "Enter" à adicionar o carácter identificador de nova linha.

É uma questão de português. Ele realmente adiciona o caracter porque ele está lá no buffer. Quando é referido que o fgets adiciona o caracter, é relativo à diferença entre a função fgets e a gets

IRC : sim, é algo que ainda existe >> #p@p
Posted (edited)

Decidi abordar os restantes pontos relativos a eficiência da aplicação em outro post, visto que o anterior já estava a tornar-se

demasiado grande e complicado de gerir a escrita no mesmo.

o uso da função memset é bom, contudo para este caso em particular completamente desnecessário,

visto que apenas interessa garantir que os dados irão ser escritos desde o inicio da array, e que um terminador seja

introduzido no final da string. logo iniciar o primeiro elemento com o terminador seria mais que suficiente neste caso.


p1[0]='\0';
p2[0]='\0';
p3[0]='\0';
p4[0]='\0';

Outro Ponto sobre strcat

Esta função não toma nenhuma medida de prevenção de overflow( passar o tamanho máximo ) da array.

portanto, o teu programa poderá ter um comportamento indefinido com base no input fornecido ao mesmo, visto

que se o mesmo for grande o suficiente poderão ser concatenados mais dados do que aqueles que podem armazenados

efectivamente.

logo um uso correcto da mesma passaria por fazer o seguinte


//o máximo que p1, p2, p3, p4 podem armazenar é 40 logo
//criamos é uma constante

//size_t porque strlen retorna size_t e não int

size_t tamanho_s1  =  0;
size_t tamanho_tmp  =  0;

//antes de concatenar as strings

tamanho_p1 = strlen(p1);

tamanho_tmp = strlen(p2);

if(  ( tamanho_s1 + tamanho_tmp ) < 40 )
{
//quer dizer que há espaço suficiente
tamanho_p1 += tamanho_tmp; //o novo tamanho é igual a soma de ambas strings
strcat(p1, p2);
}
else
{
printf("Erro, p1 não tem espaço suficiente");
exit(1); // sai do programa
}

tamanho_tmp = strlen(p3);

if( (tamanho_s1 + tamanho_tmp) < 40 )
{
 tamanho_p1 += tamanho_tmp;
 strcat(p1, p3);
}
else
{
printf("Error, p1 não tem espaço suficiente");
exit(1);
}

// e assim sucessivamente.

contudo isto leva nos a outra questão de eficiência.

strcat "procura" pelo terminador, contudo se para usar de forma segura strcat

temos que saber o tamanho da string em ante mão, já sabemos onde esta o terminador

ora, vai estar na posição tamanho_p1 + 1; logo, podemos tentar optimizar um pouco

e levar a função strcat "pensar" que esta próxima do fim da array

exemplo


// com todos os procedimentos de segurança anteriores

strcat( &p1[tamanho_p1], p2);
// este método faz com que strcat entre já no fim de p1, logo encontrando o terminador
// muito mais rápido do que se tivesse que o procurar

E por fico aqui por agora.

Espero ter ajudado, comprimentos dbs

sabes quantas vezes já foi descrito aqui no forum o porque do uso do fgets em vez do gets ?

É uma questão de português. Ele realmente adiciona o caracter porque ele está lá no buffer. Quando é referido que o fgets adiciona o caracter, é relativo à diferença entre a função fgets e a gets

Boas, se já foi descrita tantas vezes, não seria mais correcto indicar o post invés do método pelo qual optou ?

Algo que já me deparei é que existem vários posts sobre os mesmos tópicos e ninguém pareceu importar-se muito

em redireccionar para os antigos. Sou novo por aqui, e apenas estou a tentar dar a melhor ajuda que sei, da forma

mais informativa possível.

Quanto a questão do português tenho de discordar. Dizer que a função faz algo, implica que parte da sua funcionalidade impõe tal feito. Já dizer que a mesma em certa circunstancia faz algo, implica que mesmo não sendo parte da sua raiz (propósito) pode acontecer em certas circunstancias.

Apenas digo isto porque mais tarde pode levar a sérios "bugs" no raciocínio e na construção de programas

Edited by dardevelin

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.