환경은 본질적으로 문자열에 대한 포인터 배열이라는 것을 이해합니다. 따라서 이러한 문자열이 차지하는 메모리를 해제하면 해당 환경 변수가 손실됩니다.
환경 문자열에 메모리를 할당한 다음 이를 사용하여 putenv()
기존 변수를 설정하면 메모리 누수가 발생합니까?
다음 C 프로그램에서 보여줍니다.
char ** environ;
main()
{
/*
Code for printing all environment variables
*/
char * temp = (char *)malloc( 64 * sizeof(char));
strcat(temp, "");
strcat(temp, "PWD=/home/mycomputer/");
putenv(temp);
/*
Code for printing all environment variables
*/
}
이 PWD
환경 변수는 이미 일부 메모리(경로 포함)를 가리키고 있었고 이제 더 많은 메모리(경로 포함)를 할당했습니다. 따라서 포인터가 새 위치로 이동하고 이전 메모리에 액세스할 수 없지만 여전히 할당됩니다. 그렇다면 프로그램에 메모리 누수가 있다고 말하는 것이 맞습니까? 그렇지 않다면 이 변경 사항이 메모리에 어떻게 수용됩니까?
답변1
당신의 관찰은 정확합니다. 할당한 새 문자열이 malloc
이전 문자열을 대체하고 이전 문자열은 더 이상 사용되지 않습니다. 이전 문자열은 프로그램이 시작될 때 전달된 환경의 일부인 경우 스택 세그먼트의 일부입니다(즉, 프로세스에서 변수가 처음 변경되는 경우).
이것이 메모리 누수인지 여부는 논쟁의 여지가 있습니다. 이전 문자열이 차지한 메모리를 해제할 수는 없지만 해당 메모리를 재사용할 수 있는 합리적인 방법도 없습니다. putenv
여러 번 호출 하면 free
문자열이 더 이상 사용되지 않을 때 자체 할당된 메모리를 사용할 수 있습니다.
putenv
문서에 명시된 대로 glibc 버전에 따라 의미가 달라집니다.매뉴얼 페이지.
그런데, 대신 프로그램에 버그가 있습니다
strcat(temp, "");
strcat(temp, "PWD=/home/mycomputer/");
너는 써야 해
strcpy(temp, "PWD=/home/mycomputer/");
답변2
환경은 세 부분으로 구성됩니다.
envp와 같은 이중 포인터를 이라고 합니다
environment list pointer
.을 가리키며
environment list
, 환경 문자열을 가리키는 포인터 배열로 생각할 수 있습니다.,
environment strings
형태name=value
.
이제 프로세스의 가상 머신 레이아웃에서 어디에 저장됩니까?
대답은 - 스택에서 호출할 수 있습니다.더 높은 주소.
하나 있다콘크리트 천장과 콘크리트 바닥;위에서는 레이아웃의 천장이므로 확장할 수 없습니다. 아래에서는 스택으로 인해 확장할 수 없습니다.
이제 귀하의 질문에 대답하려면 -
내부적으로는 문자열을 더 높은 세그먼트의 길이보다 큰 값으로 변경할 때 이 작업을 수행합니다 malloc
.
그러면 환경 목록의 포인터가 할당된 메모리를 가리키도록 변경됩니다. 따라서 일부 포인터가 여전히 더 높은 메모리의 주소를 가리키는 환경 목록이 있고 이 특정 포인터는 힙의 일부 영역을 가리킵니다.. 이 특별한 경우에는개정하다매개변수의 전체 환경 목록은 힙에 복사되지 않습니다.
포인터가 가리키는 이전 메모리는 어떻게 되나요? 글쎄요것 같다메모리 누수와 유사하지만 일반적으로 그렇지 않습니다.방법을 제공, 해당 메모리는 이미 존재하고 이제 쓸모 없는 블록이므로 더 이상 가리킬 수 없습니다.
동적 할당이 아니라 이미 예약된 세그먼트의 일부인 정적 할당이기 때문에 메모리 누수라고 부르지는 않습니다.
메모리 누수란 메모리의 일부가 다른 용도로 사용할 수 있지만 액세스할 수 없기 때문에 사용할 수 없는 것처럼 들립니다. 그러나 어떤 경우에도 상위 주소에 있는 이 메모리 조각은 애초에 다른 목적으로 사용될 수 없습니다.
따라서 메모리 누수처럼 보이지만 일반적으로 그렇게 부르지는 않습니다.
혼란을 드려 죄송합니다. 최대한 명확하게 설명하려고 노력했습니다 :)
답변3
첫 번째 버전에서 질문을 수정한 것이 안타깝습니다 setenv()
.setenv()
하다적어도 glibc 구현에서는 메모리 누수가 발생합니다.
예를 들어 다음 샘플 프로그램을 사용하면 메모리가 부족해질 때까지 계속해서 커집니다.
#include <stdlib.h>
#include <stdio.h>
int main(void){
char buf[24];
for(;;){
snprintf(buf, sizeof buf, "%d", rand());
setenv("foo", buf, 1);
}
}
glibc 구현에서는 setenv()
이전에 할당된 메모리가 해제되지 않습니다 setenv()
. 그러나 이진 트리에서 할당된 환경 문자열을 추적하여 중복을 방지합니다( tfind(3)
목록과 별도로 사용 char **environ
). 이는 누출과 같은 도구를 숨기는 좋은 효과도 있습니다 valgrind
;-)
그에 관해서는 putenv()
인수로 전달된 문자열을 관리하기 위해 호출자에 의존합니다. 따라서 호출자는 반복 호출 시 누출되지 않는지 putenv()
, 환경의 일부인 동안 릴리스되지 않는지, 자동/스택 변수를 사용하지 않는지 확인해야 합니다.
또한 문자열을 호출한 후 수정하면 putenv()
환경 변수가 제거되거나 다른 환경 변수가 추가될 수 있습니다. 예:
#define _XOPEN_SOURCE 500
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <unistd.h>
int main(void){
extern char **environ;
static char *my_environ[] = { "YUCK=yumm", 0 };
static char str[256] = "FOO=bar";
environ = my_environ;
putenv(str);
snprintf(str, sizeof str, "BAZ=quux");
execl("/usr/bin/printenv", "printenv", (void*)0);
err(1, "execlp");
}
이는 표준에서 요구하지만 putenv()
인수를 복제하는 이전 버전의 glibc에서는 작동하지 않습니다.
마지막으로 환경에 새 변수가 추가되면 setenv()
및 putenv()
배열이 모두 재배치됩니다 . char **environ
구현은 포인터의 자체 복사본을 유지하고 로 확장 realloc()
하지만 원래 포인터(정적 메모리를 가리키는)를 손상시키거나 수동으로 할당된 메모리를 사용하지 않습니다. environ = calloc(sizeof *environ, envlen)
.