2008-08-04 5 views
11

을 사용하여 할당 된 char *에서 작동하는 C에서 문자열 바꾸기 함수를 작성하려고했습니다. 시작 문자열에있는 문자가 아니라 문자열을 찾아서 바꿀 수 있다는 점에서 조금 다릅니다.realloc()에 이중 자유 오류가 발생하는 이유는 무엇입니까?

충분한 공간이 할당되어 있기 때문에 검색 및 바꾸기 문자열이 동일한 길이 (또는 바꾸기 문자열이 검색 문자열보다 짧으면)하는 것이 쉽습니다. realloc()을 사용하려고하면 double free를 수행하고 있음을 알리는 오류가 발생합니다. realloc() 만 사용하고 있기 때문에 나는 어떻게 보이지 않습니다.

아마도 약간의 코드가 도움이 될 것입니다

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); 
    } 
} 

이 프로그램은 작동, 나는 대체 문자열이 초기 문자열보다 더 길어질 수 인스턴스에 realloc()하려고 할 때까지. (그것은 여전히 ​​일종의 작품이며, 결과뿐만 아니라 오류도 뱉어냅니다.) 도움이된다면

는 호출 코드가 보이는 같은 :

#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"); 
    } 
} 

답변

11

일반적으로 은 사용자가 제공 한 버퍼에서 무료 또는 realloc을 수행해야합니다. 사용자 버퍼에서 할당 함수를 사용할 수 없도록 사용자가 공간을 할당 한 위치 (다른 DLL에서 모듈에 있음)를 알지 못합니다.

함수 내에서 재 할당을 할 수 없다면 하나의 대체 작업 만하는 것처럼 동작을 약간 변경해야하므로 사용자는 결과 문자열 최대 길이를 계산하고 버퍼를 길게 제공 할 수 있습니다 이 한 번의 교체가 일어나기에 충분합니다.

그런 다음 여러 대체를 수행하는 다른 함수를 만들 수 있지만 결과 문자열의 전체 공간을 할당하고 사용자 입력 문자열을 복사해야합니다. 그런 다음 할당 한 문자열을 삭제하는 방법을 제공해야합니다.

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

나는 아직 그것을 시도하지 않은하지만 당신은 realloc 함수 때 훨씬의 malloc 같은 포인터를 반환하기 때문에 어둠 속에서 그냥 촬영. ,

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

realloc이 실패하면 NULL을 반환하고 기존 버퍼를 그대로 둡니다. 방금 포인터를 잃어 버렸습니다 : :-( –

4

참고 html로 이스케이프 코드를 제거하는 코드를 편집하려고 : 필요한 경우 realloc을 포인터를 이동할 수 있기 때문에 다음을 수행하지 않는 경우가 대부분 유효하지 않은 포인터를 운영하고 있습니다.

글쎄, C/C++를 사용한 이후로 꽤 오래되었지만 realloc은 원래 블록 다음에 메모리가있을 경우에만 메모리 포인터 값을 재사용합니다.

예를 들어,이 사항을 고려하십시오

(XXXXXXXXXX ..........)

경우 첫 번째 x와 포인터 포인트합니다. 여유 메모리 위치를 의미하고 변수가 가리키는 메모리 크기가 5 바이트 증가하면 성공할 것입니다. 이것은 블록이 정렬을 위해 특정 크기로 반올림되었지만 어쨌든 단순화 된 예제입니다.

그러나 나중에 다른 10 바이트 씩 늘리면 사용 가능한 메모리가 5 개 뿐이므로 메모리에서 블록을 이동하고 포인터를 업데이트해야합니다.

그러나 예에서 변수에 대한 포인터가 아닌 포인터에 함수 포인터를 전달하므로 strrep 함수가 내부적으로 사용중인 변수를 조정할 수있는 반면 변수는 로컬 변수입니다. strrep 함수와 호출 코드는 원래 포인터 변수 값으로 남게됩니다.

그러나이 포인터 값은 해제되었습니다.

귀하의 경우 입력이 원인입니다.

그러나 나는 또 다른 제안을 할 것입니다. 귀하의 경우에는 입력 변수가 실제로 입력 된 것처럼 보입니다. 그렇다면 변수가 전혀 수정되어서는 안됩니다.

입력을 변경하지 않고도 원하는대로 할 수있는 다른 방법을 찾으려고합니다. 이와 같은 부작용을 추적하기가 어려울 수 있습니다.

0

내 빠른 힌트 : 결과

.

대신 :
void strrep(char *input, char *search, char *replace)
보십시오
input = realloc(input, strlen(input) + delta);

일반적 값/레퍼런스 및 realloc을() 설명으로서 기능 인자를 전달 읽어 : void strrep(char *&input, char *search, char *replace)

신체보다
:).

+0

'void strrep (char * & input, char * search, char * replace) '는 C에서는 유효하지 않지만 C++에서는 유효합니다. , 그리고 AFAICT는 C++로 태그 지어지지 않았습니다. 최선을 다해서, 코드는'void strrep (char ** input, char * search, char * replace)'가되어야 합니다만,'char * strrep (const char * 입력, const char * search, const char * replace)'실행할 수있는 인터페이스입니다 (입력 문자열은 변경되지 않고 수정 된 문자열이 할당되어 반환됩니다.) –

3

이것은 작동하는 것 같습니다.

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; 
} 

어쨌든, 빠져 나가지 않고 코드를 게시 할 수 있습니까?

+0

주석을 추가 할 때 주석이 답변으로 작성되었으므로, 주석 처리하기 전에 : 첫 번째 사건 만 바꾼 것처럼 보입니다. 모든 사건이 바뀌어야한다고 진술하지 않았기 때문에 아마도 합리적 일 것입니다! –

12

우선, 미안하지만 나는 파티에 늦었습니다. 이것은 내 첫 stackoverflow 대답이다. :)

realloc()이 호출 될 때, 포인터를 재 할당 할 메모리로 잠재적으로 변경할 수 있습니다. 이 경우 "string"인수가 유효하지 않게됩니다. 다시 할당하더라도 기능이 끝나면 변경 내용이 범위를 벗어납니다.

OP에 대답하기 위해 realloc()은 새로 할당 된 메모리에 대한 포인터를 반환합니다. 반환 값은 어딘가에 저장해야합니다. 일반적으로, 당신은이 작업을 수행 할 것입니다 : TyBoer 지적

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); 
} 

으로, 너희들은 포인터의 값이 함수의 입력으로 전달되고 변경할 수 없습니다. 당신이 원하는 것을 할당 할 수 있지만, 변경은 기능의 끝에서 범위를 벗어날 것입니다.

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)); 
} 

마크 함수의 출력으로 새로운 포인터를 반환하여이 문제를 해결하려고 : 함수가 완료되면 다음 블록에서 "입력"또는 유효하지 않은 포인터가 될 수도 있고 그렇지 않을 수도 있습니다. 그렇게하면 입력에 사용한 포인터를 다시 사용하지 않도록 호출자가 수행해야합니다.반환 값과 일치하면 동일한 지점에 두 포인터가 있으며 그 중 하나에서 free()를 호출하면됩니다. 일치하지 않으면 입력 포인터가 이제 프로세스가 소유하거나 소유하지 않을 수있는 메모리를 가리 킵니다. 이를 참조 해제하면 분할 오류가 발생할 수 있습니다.

이 같은 입력에 대한 이중 포인터를 사용할 수 있습니다

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

을 호출자가 어딘가에 입력 포인터의 중복이있는 경우, 여전히 중복이 이제 유효하지 않을 수 있습니다.

여기서 가장 깨끗한 해결책은 함수 호출자의 입력을 수정할 때 realloc()을 사용하지 않는 것입니다. 새로운 버퍼를 malloc()하여 반환하고, 호출자가 이전 텍스트를 해제할지 여부를 결정하게합니다. 발신자가 원래 문자열을 유지하도록하는 이점이 있습니다!

6

다른 사람이 파티에 늦게 사과 한 지 두 달 반이 지났습니다. 오, 저는 소프트웨어 고고학을하는 데 많은 시간을 할애합니다.

아무도 원래 디자인의 메모리 누수 또는 off-by-one 오류에 대해 명시 적으로 주석을다는 것이 좋습니다. 그리고 정확히 왜 이중 자유 오류가 발생 하는지를 알려주는 메모리 누수를 관찰하고있었습니다 (정확하게 말하면 동일한 메모리를 여러 번 해제하기 때문에 이미 해제 된 메모리를 밟고 있기 때문입니다).

분석을 수행하기 전에 인터페이스가 별보다 작다고 말하는 사람들과 동의합니다. 그러나 메모리 누출/트램 핑 문제를 다루고 '할당 된 메모리 여야 함'요구 사항을 문서화 한 경우 'OK'일 수 있습니다.

무엇이 문제입니까? realloc()에 버퍼를 전달하면 realloc()은 사용할 영역에 대한 새 포인터를 반환하고 반환 값은 무시합니다. 결과적으로 realloc()은 원래 메모리를 해제 한 다음 동일한 포인터를 다시 전달하고 원래 값을 다시 전달하기 때문에 동일한 메모리를 두 번 해제한다고 불평합니다. 이것은 기억을 누설 할뿐만 아니라 원래의 공간을 계속 사용하고 있음을 의미합니다. 어두운 곳에서 John Downey의 사진은 realloc()을 잘못 사용하고 있음을 지적하지만 너무 심각하게 강조하지는 않습니다. 문자열을 종료하는 NUL '\ 0'에 충분한 공간을 할당하지 않았기 때문에 오프 - 바이 - 원 오류가 발생합니다.

메모리 누수는 호출자에게 문자열의 마지막 값을 알리는 메커니즘을 제공하지 않기 때문에 발생합니다. 원래 문자열과 그 뒤의 공백을 계속 짓밟 았기 때문에 코드가 작동하는 것처럼 보이지만 호출 코드가 공간을 비우면 이중 자유 오류가 발생하거나 코어 덤프 나 동등 물이 생길 수 있습니다. 메모리 제어 정보가 완전히 스크램블된다.

귀하의 코드는 무기한 성장을 방지하지 못합니다. 'Noel'을 'Joyeux Noel'로 바꾸는 것을 고려하십시오. 매번 7자를 추가 할 것이지만 대체 된 텍스트에 다른 Noel을 찾아 확장하는 등의 작업을 수행 할 수 있습니다. 내 픽스 업 (아래)에서는이 문제를 해결하지 못합니다. 간단한 해결책은 검색 문자열이 대체 문자열에 나타나는지 확인하는 것입니다. 대체 문자열을 건너 뛰고 그 후에 검색을 계속하는 것입니다. 두 번째 문제는 다루기가 어렵지 않은 코딩 문제가 있습니다.

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); 
} 

이 코드는 메모리 할당 오류를 감지하지 않습니다 - 아마도 실패) (realloc을하는 경우 충돌 (그러나 경우, 메모리 누수) :

그래서, 당신의 호출 된 함수 내 제안 개정이다. Steve Maguire의 '솔리드 코드 작성'책에서 메모리 관리 문제에 대한 광범위한 토론을 볼 수 있습니다.

+1

감사합니다. 이것은 내가 뭘 잘못하고 있었는지에 대한 정말 좋은 분석입니다. 더블 프리 (double-free)는 어떤면에서 제가 잘못하고있는 몇 가지 것들의 부산물이라는 의미입니다.) realloc()이 방금 메모리 할당을 확장했다는 생각이 들었습니다. 생각할 때 전혀 이해가되지 않습니다! –

3

realloc은 이상하고 복잡하며 초당 많은 메모리를 사용할 때만 사용해야합니다. 즉, 실제로 코드를 더 빠르게 만들 수 있습니다.

realloc(bytes, smallerSize); 

사용 및 버퍼 크기를 조정하기 위해 노력했다 어디

나는 작게, 코드를 보았다. 수백만 번 작업 한 후, 어떤 이유로 realloc은 버퍼를 줄이는 경우에도 새로운 복사본을 얻을 수 있다고 판단했습니다. 그래서 나쁜 일이 일어난 후 1/2 초에 무작위로 충돌합니다.

항상 realloc의 반환 값을 사용하십시오.

관련 문제