TransWikia.com

"strcpy()" está mesclando formato numérico com outros chars

Stack Overflow em Português Asked on November 10, 2021

Eu não sei se consegui me fazer entender no título, mas ao usar strcpy() para copiar um char* para outro quando coloco um formato assim "teste" ele funciona normalmente, mas quando coloco uma string com formato 3 letras (dígitos no caso), por exemplo "2000" ele acaba mesclando esse valor para o destino com o próximo valor da próxima vez que uso strcpy(), segue o código:

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

typedef struct
{
    char nome[80];
    char ano[4];
    char diretor[80];
} Filme;

void analise(Filme *filme, const char *arg1, const char *arg2, const char *arg3)
{
    Filme _filme = {
        .nome    = malloc(2),
        .ano     = malloc(2),
        .diretor = malloc(2)
    };

    strcpy(_filme.nome, arg1);
    strcpy(_filme.ano, arg2);
    strcpy(_filme.diretor, arg3);

    memcpy(filme, &_filme, sizeof _filme);
}


void carregar(Filme filmes[])
{
    analise(&filmes[0], "E o Vento Levou", "1939", "Victor");
    analise(&filmes[1], "teste", "998", "bar");
    analise(&filmes[2], "Os Passaros", "1963", "Alfred Hitchcock");
}

int main()
{
    Filme filmes[1000];

    carregar(filmes);

    printf("nmain:n");

    printf("- Nome:    %sn", filmes[0].nome);
    printf("- Ano:     %sn", filmes[0].ano);
    printf("- Diretor: %sn", filmes[0].diretor);

    printf("----------------n");

    printf("- Nome:    %sn", filmes[1].nome);
    printf("- Ano:     %sn", filmes[1].ano);
    printf("- Diretor: %sn", filmes[1].diretor);

    printf("----------------n");

    printf("- Nome:    %sn", filmes[2].nome);
    printf("- Ano:     %sn", filmes[2].ano);
    printf("- Diretor: %sn", filmes[2].diretor);

    return 0;
}

Notem que executei isto:

analise(&filmes[0], "E o Vento Levou", "1939", "Victor");
analise(&filmes[1], "teste", "998", "bar");
analise(&filmes[2], "Os Passaros", "1963", "Alfred Hitchcock");

Ao executar o problema a saída é esta:

main:
- Nome:    E o Vento Levou
- Ano:     1939Victor
- Diretor: Victor
----------------
- Nome:    teste
- Ano:     998
- Diretor: bar
----------------
- Nome:    Os Passaros
- Ano:     1963Alfred Hitchcock
- Diretor: Alfred Hitchcock

Vejam que em “E o Vento Levou” e “Os Passaros” os anos ficaram mesclados com o nome do diretor, 1939Victor e 1963Alfred Hitchcock, já no caso do:

analise(&filmes[1], "teste", "998", "bar");

Tem a saída correta. Eu entendo que deveria fazer o ano com int, mas estou aprendendo C e gostaria de entender melhor esta parte da memória, presumo que tenha sido algum erro de digitação meu.

2 Answers

O que ocorre é que o seu struct ocupa 164 bytes na memória:

  • 80 bytes para o nome, incluindo o terminador nulo;
  • 4 bytes para o ano, você não está considerando o terminador nulo aqui;
  • 80 bytes para o diretor, incluindo o terminador nulo.

O compilador vai atribuir os seguintes deslocamentos para cada um desses campos:

  • nome: 0 bytes de deslocamento desde o início da struct na memória.
  • ano: 80 bytes de deslocamento desde o início da struct na memória.
  • diretor: 84 bytes de deslocamento desde o início da struct na memória.

Assim sendo, se pergarmos o primeiro caso por exemplo, o layout fica assim (onde ∅ é o terminador nulo e □ é lixo, que pode ter qualquer valor):

E o Vento Levou∅□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□1939Victor∅□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□

Quando você faz isso:

printf("- Ano:     %sn", filmes[0].ano);

Em tempo de execução, o código gerado vai carregar o endereço de memória de filmes, somar 164×0 para obter filmes[0] (164 = sizeof Filme, 0 é o índice) e então somar 80 para obter a posição de filmes[0].ano (80 é a posição do ano dentro do struct). Dessa posição ele vai imprimir uma string (devido ao %s) que começa nessa posição calculada e termina no primeiro terminador nulo encontrado. Ocorre que o ano não tem um terminador nulo, e portanto ele vai acabar invadindo a região de memória subsequente do campo diretor.

Além disso, esse código não têm nenhum sentido:

Filme _filme = {
    .nome    = malloc(2),
    .ano     = malloc(2),
    .diretor = malloc(2)
};

O que você queria provavelmente era isso:

void analise(Filme *filme, const char *arg1, const char *arg2, const char *arg3) {
    strcpy(filme->nome, arg1);
    strcpy(filme->ano, arg2);
    strcpy(filme->diretor, arg3);
}

Para resolver o seu problema sem usar int para o ano, uma possibilidade é mudar o tamanho do campo ano para 5. Se os demais campos devem ter no máximo 80 caracteres ao invés de 79, então mude o tamanho deles para 81 a fim de se certificar que o terminador nulo está lá.

Outra alternativa que evita ter que redimensionar o tamanho dos campos é especificar uma tamanho máximo para as strings no printf:

printf("- Nome:    %.80sn", filmes[0].nome);
printf("- Ano:     %.4sn", filmes[0].ano);
printf("- Diretor: %.80sn", filmes[0].diretor);

Answered by Victor Stafusa on November 10, 2021

Este código tem alguns problemas.

  • Não tem porque usar malloc() se a área que a string deve ficar já está reservada dentro da estrutura. Até poderia alocar se desejar, e talvez faça sentido para os nomes, mas aí precisa declarar como const char * e não [tamanho]. Tem que ser bem mais cuidadoso quando faz isso. O que inclusive está vazando memória. Em grande volume isso seria trágico. E alocar apenas 2 caracteres onde precisa de vários também não dá muito certo, é que neste caso coincidentemente funciona.
  • Não que seja um problema, mas não vejo motivo para criar uma estrutura local, inicializar seus membros e depois copiar para o array. Escreva diretamente no array, sem criar nada intermediário, inicializar ou copiar.
  • As strings possuem um terminador, então precisa reservar espaço para ele. O problema específico que está encontrando é que tem 4 bytes reservados para o ano, então os 4 caracteres do ano são colocados ali, e um 5o. é colocado em seguida. Quando coloca o nome do diretor, o seu primeiro caractere vai em cima do terminador da string do ano. Aí quando vai ler o ano ele não tem fim, só finalizaria no fim do nome do diretor, por isso fica tudo junto. Se tivesse 5 bytes, o terminador seria preservado e tudo funcionaria ok.

Assim funciona:

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

typedef struct {
    char nome[81];
    char ano[5];
    char diretor[81];
} Filme;

void analise(Filme *filme, const char *arg1, const char *arg2, const char *arg3) {
    strcpy(filme->nome, arg1);
    strcpy(filme->ano, arg2);
    strcpy(filme->diretor, arg3);
}

void carregar(Filme filmes[]) {
    analise(&filmes[0], "E o Vento Levou", "1939", "Victor");
    analise(&filmes[1], "teste", "998", "bar");
    analise(&filmes[2], "Os Passaros", "1963", "Alfred Hitchcock");
}

int main() {
    Filme filmes[1000];
    carregar(filmes);
    printf("nmain:n");
    for (int i = 0; i < 3; i++) {
        printf("- Nome:    %sn", filmes[i].nome);
        printf("- Ano:     %sn", filmes[i].ano);
        printf("- Diretor: %sn", filmes[i].diretor);
        printf("----------------n");
    }
}

Veja funcionando no ideone. E no repl.it. Também coloquei no GitHub para referência futura.

Answered by Maniero on November 10, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP