Pergunta

Eu tentei escrever uma função de substituição de string em C, que funciona em um char *, que foi alocado usando malloc().É um pouco diferente porque encontrará e substituirá strings, em vez de caracteres na string inicial.

É trivial fazer isso se as strings de pesquisa e substituição tiverem o mesmo comprimento (ou se a string de substituição for menor que a string de pesquisa), pois tenho espaço suficiente alocado.Se eu tentar usar realloc(), recebo um erro que me diz que estou fazendo uma liberação dupla - o que não vejo como estou, pois só estou usando realloc().

Talvez um pequeno código ajude:

void strrep(char *input, char *search, char *replace) {
    int searchLen = strlen(search);
    int replaceLen = strlen(replace);
    int delta = replaceLen - searchLen;
    char *find = input;

    while (find = strstr(find, search)) {

        if (delta > 0) {
            realloc(input, strlen(input) + delta);
            find = strstr(input, search);            
        }

        memmove(find + replaceLen, find + searchLen, strlen(input) - (find - input));
        memmove(find, replace, replaceLen);
    }
}

O programa funciona, até que eu tento realloc() em uma instância em que a string substituída será maior que a string inicial.(Ainda funciona, apenas exibe erros e também o resultado).

Se ajudar, o código de chamada será semelhante a:

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

void strrep(char *input, char *search, char *replace);

int main(void) {
    char *input = malloc(81);

    while ((fgets(input, 81, stdin)) != NULL) {
        strrep(input, "Noel", "Christmas");
    }
}
Foi útil?

Solução

Como regra geral, você deve nunca faça um free ou realloc em um buffer fornecido pelo usuário.Você não sabe onde o usuário alocou o espaço (no seu módulo, em outra DLL), portanto não pode usar nenhuma das funções de alocação em um buffer do usuário.

Desde que agora você não possa fazer nenhuma realocação dentro de sua função, você deve mudar um pouco seu comportamento, como fazer apenas uma substituição, para que o usuário possa calcular o comprimento máximo da string resultante e fornecer um buffer longo o suficiente para este. ocorra a substituição.

Então você poderia criar outra função para fazer as múltiplas substituições, mas terá que alocar todo o espaço para a string resultante e copiar a string de entrada do usuário.Então você deve fornecer uma maneira de excluir a string alocada.

Resultando em:

void  strrep(char *input, char *search, char *replace);
char* strrepm(char *input, char *search, char *replace);
void  strrepmfree(char *input);

Outras dicas

Primeiro, desculpe, estou atrasado para a festa.Esta é minha primeira resposta stackoverflow.:)

Como foi apontado, quando realloc() é chamado, você pode potencialmente alterar o ponteiro para a memória que está sendo realocada.Quando isso acontece, o argumento “string” torna-se inválido.Mesmo se você reatribuí-la, a alteração sairá do escopo quando a função terminar.

Para responder ao OP, realloc() retorna um ponteiro para a memória recém-realocada.O valor de retorno precisa ser armazenado em algum lugar.Geralmente, você faria isso:

data *foo = malloc(SIZE * sizeof(data));
data *bar = realloc(foo, NEWSIZE * sizeof(data));

/* Test bar for safety before blowing away foo */
if (bar != NULL)
{
   foo = bar;
   bar = NULL;
}
else
{
   fprintf(stderr, "Crap. Memory error.\n");
   free(foo);
   exit(-1);
}

Como aponta TyBoer, vocês não podem alterar o valor do ponteiro que está sendo passado como entrada para esta função.Você pode atribuir o que quiser, mas a alteração sairá do escopo no final da função.No bloco a seguir, "input" pode ou não ser um ponteiro inválido quando a função for concluída:

void foobar(char *input, int newlength)
{
   /* Here, I ignore my own advice to save space. Check your return values! */
   input = realloc(input, newlength * sizeof(char));
}

Mark tenta contornar isso retornando o novo ponteiro como saída da função.Se você fizer isso, a responsabilidade recairá sobre o chamador para nunca mais usar o ponteiro que ele usou para entrada.Se corresponder ao valor de retorno, você terá dois ponteiros para o mesmo local e só precisará chamar free() em um deles.Se não corresponderem, o ponteiro de entrada agora aponta para a memória que pode ou não pertencer ao processo.Desreferenciar isso pode causar uma falha de segmentação.

Você poderia usar um ponteiro duplo para a entrada, assim:

void foobar(char **input, int newlength)
{
   *input = realloc(*input, newlength * sizeof(char));
}

Se o chamador tiver uma duplicata do ponteiro de entrada em algum lugar, essa duplicata ainda poderá ser inválida agora.

Acho que a solução mais limpa aqui é evitar o uso de realloc() ao tentar modificar a entrada do chamador da função.Apenas malloc() um novo buffer, retorne-o e deixe o chamador decidir se deve ou não liberar o texto antigo.Isso tem o benefício adicional de permitir que o chamador mantenha a string original!

Apenas um tiro no escuro porque ainda não tentei, mas quando você realoca, ele retorna o ponteiro como o malloc.Como realloc pode mover o ponteiro, se necessário, você provavelmente estará operando com um ponteiro inválido se não fizer o seguinte:

input = realloc(input, strlen(input) + delta);

Outra pessoa se desculpou pelo atraso na festa - há dois meses e meio.Bem, passo muito tempo fazendo arqueologia de software.

Estou interessado em saber que ninguém comentou explicitamente sobre o vazamento de memória no design original ou sobre o erro off-by-one.E foi a observação do vazamento de memória que me diz exatamente por que você está recebendo o erro de liberação dupla (porque, para ser mais preciso, você está liberando a mesma memória várias vezes - e está fazendo isso depois de atropelar a memória já liberada).

Antes de realizar a análise, concordo com aqueles que dizem que sua interface não é nada excelente;no entanto, se você lidou com os problemas de vazamento/atropelamento de memória e documentou o requisito de 'memória deve ser alocada', pode ser 'OK'.

Quais são os problemas?Bem, você passa um buffer para realloc(), e realloc() retorna um novo ponteiro para a área que você deve usar - e você ignora esse valor de retorno.Conseqüentemente, realloc() provavelmente liberou a memória original e, em seguida, você passa o mesmo ponteiro novamente e reclama que você está liberando a mesma memória duas vezes porque passou o valor original para ela novamente.Isso não apenas vaza memória, mas significa que você continua usando o espaço original - e o tiro no escuro de John Downey aponta que você está usando indevidamente realloc(), mas não enfatiza o quão severamente você está fazendo isso.Há também um erro off-by-one porque você não aloca espaço suficiente para o NUL '\0' que finaliza a string.

O vazamento de memória ocorre porque você não fornece um mecanismo para informar ao chamador sobre o último valor da string.Como você continuou atropelando a string original mais o espaço depois dela, parece que o código funcionou, mas se o seu código de chamada liberasse o espaço, ele também receberia um erro de liberação dupla ou poderia obter um core dump ou equivalente porque as informações de controle de memória estão completamente embaralhadas.

Seu código também não protege contra crescimento indefinido - considere substituir 'Noel' por 'Joyeux Noel'.Cada vez, você adicionaria 7 caracteres, mas encontraria outro Noel no texto substituído e o expandiria, e assim por diante.Minha correção (abaixo) não resolve esse problema - a solução simples provavelmente é verificar se a string de pesquisa aparece na string de substituição;uma alternativa é pular a string de substituição e continuar a pesquisa depois dela.O segundo tem alguns problemas de codificação não triviais para resolver.

Então, minha sugestão de revisão da sua função chamada é:

char *strrep(char *input, char *search, char *replace) {
    int searchLen = strlen(search);
    int replaceLen = strlen(replace);
    int delta = replaceLen - searchLen;
    char *find = input;

    while ((find = strstr(find, search)) != 0) {
        if (delta > 0) {
            input = realloc(input, strlen(input) + delta + 1);
            find = strstr(input, search);            
        }

        memmove(find + replaceLen, find + searchLen, strlen(input) + 1 - (find - input));
        memmove(find, replace, replaceLen);
    }

    return(input);
}

Este código não detecta erros de alocação de memória - e provavelmente trava (mas se não, vaza memória) se realloc() falhar.Veja o livro 'Writing Solid Code' de Steve Maguire para uma extensa discussão sobre problemas de gerenciamento de memória.

Observe, tente editar seu código para se livrar dos códigos de escape HTML.

Bem, embora já faça um tempo desde que usei C/C++, realloc que cresce apenas reutiliza o valor do ponteiro de memória se houver espaço na memória após o bloco original.

Por exemplo, considere isto:

(xxxxxxxxxx..........)

Se o seu ponteiro apontar para o primeiro x e .significa local de memória livre e você aumenta o tamanho da memória apontado por sua variável em 5 bytes, terá sucesso.É claro que este é um exemplo simplificado, pois os blocos são arredondados para um determinado tamanho para alinhamento, mas de qualquer maneira.

No entanto, se posteriormente você tentar aumentá-lo em mais 10 bytes e houver apenas 5 disponíveis, será necessário mover o bloco na memória e atualizar seu ponteiro.

No entanto, no seu exemplo, você está passando para a função um ponteiro para o caractere, não um ponteiro para sua variável e, portanto, embora a função strrep internamente possa ajustar a variável em uso, ela é uma variável local para a função strrep e seu código de chamada permanecerá com o valor da variável de ponteiro original.

Esse valor de ponteiro, entretanto, foi liberado.

No seu caso, a entrada é a culpada.

No entanto, eu faria outra sugestão.No seu caso parece que entrada variável é de fato uma entrada e, se for, não deve ser modificada de forma alguma.

Eu tentaria, portanto, encontrar outra maneira de fazer o que você quer, sem mudar entrada, pois efeitos colaterais como esse podem ser difíceis de detectar.

Isso parece funcionar;

char *strrep(char *string, const char *search, const char *replace) {
    char *p = strstr(string, search);

    if (p) {
        int occurrence = p - string;
        int stringlength = strlen(string);
        int searchlength = strlen(search);
        int replacelength = strlen(replace);

        if (replacelength > searchlength) {
            string = (char *) realloc(string, strlen(string) 
                + replacelength - searchlength + 1);
        }

        if (replacelength != searchlength) {
            memmove(string + occurrence + replacelength, 
                        string + occurrence + searchlength, 
                        stringlength - occurrence - searchlength + 1);
        }

        strncpy(string + occurrence, replace, replacelength);
    }

    return string;
}

Suspiro, existe alguma maneira de postar código sem que seja uma droga?

realloc é estranho, complicado e só deve ser usado ao lidar com muita memória, muitas vezes por segundo.ou seja- onde realmente torna seu código mais rápido.

Eu vi código onde

realloc(bytes, smallerSize);

foi usado e trabalhado para redimensionar o buffer, tornando-o menor.Trabalhei cerca de um milhão de vezes e, por algum motivo, o realloc decidiu que, mesmo que você estivesse encurtando o buffer, isso lhe daria uma bela nova cópia.Então você bate em um lugar aleatório meio segundo depois que a coisa ruim aconteceu.

Sempre use o valor de retorno de realloc.

Minhas dicas rápidas.

Em vez de:
void strrep(char *input, char *search, char *replace)
tentar:
void strrep(char *&input, char *search, char *replace)

e do que no corpo:
input = realloc(input, strlen(input) + delta);

Geralmente leia sobre como passar argumentos de função como valores/referência e descrição realloc() :).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top