programing

malloc()는 연속된 메모리 블록을 할당합니까?

javajsp 2023. 10. 16. 21:30

malloc()는 연속된 메모리 블록을 할당합니까?

아주 오래된 학교 프로그래머가 쓴 코드 조각이 있어요 :-) . 이런식으로 진행됩니다.

typedef struct ts_request
{ 
  ts_request_buffer_header_def header; 
  char                         package[1]; 
} ts_request_def; 

ts_request_def* request_buffer = 
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));

프로그래머는 기본적으로 버퍼 오버플로우 개념을 연구하고 있습니다.암호가 의심스럽다는 거 알아요.그래서 제 질문은 다음과 같습니다.

  1. malloc는 항상 연속된 메모리 블록을 할당합니까? 이 코드에서 블록이 연속되지 않으면 코드가 크게 실패하기 때문입니다.

  2. 하고있다free(request_buffer)즉 malloci에 할당된 바이트를 시킬 수 요?e에 의해 할당된 모든 바이트를 자유롭게 해줄 것입니다.sizeof(ts_request_def) + (2 * 1024 * 1024) 입력할 수 있습니다. 됩니다.sizeof(ts_request_def)

  3. 이 접근법에 명백한 문제가 있습니까? 상사와 상의해야 할 사항이며 이 접근법에 허점이 있음을 지적하고 싶습니다.

당신의 번호표에 답하기 위해서요.

  1. 네.
  2. 모든 바이트.Malloc/free는 개체의 종류를 모르거나 신경 쓰지 않고 크기만 신경 씁니다.
  3. 이것은 엄밀하게 말하면 정의되지 않은 행동이지만, 많은 구현에서 지원되는 일반적인 속임수입니다.다른 대안에 대해서는 아래를 참조하십시오.

최신 C 표준인 ISO/IEC 9899:1999(비공식적으로는 C99)는 유연한 어레이 멤버를 허용합니다.

예를 들면 다음과 같습니다.

int main(void)
{       
    struct { size_t x; char a[]; } *p;
    p = malloc(sizeof *p + 100);
    if (p)
    {
        /* You can now access up to p->a[99] safely */
    }
}

이제 표준화된 이 기능을 사용하면 질문에 설명한 표준이 아닌 일반적인 구현 확장을 사용하지 않아도 됩니다.엄밀히 말하면 유연하지 않은 어레이 멤버를 사용하고 범위를 벗어난 액세스를 하는 것은 정의되지 않은 동작이지만 많은 구현이 이를 문서화하고 권장합니다.

또한 gcc는 확장으로 0-길이 배열을 허용합니다.표준 C에서는 영-길이 어레이가 불법이지만, GCC는 C99가 유연한 어레이 멤버를 제공하기 전에 이 기능을 도입했습니다.

댓글에 대한 응답으로 아래 토막글이 기술적으로 정의되지 않은 동작인 이유를 설명하겠습니다.제가 인용하는 섹션 번호는 C99 (ISO/IEC 9899:1999)를 참조합니다.

struct {
    char arr[1];
} *x;
x = malloc(sizeof *x + 1024);
x->arr[23] = 42;

먼저, 6.5.2.1#2는 a[i]가 (*(a)+(i))와 동일하므로 x->arr[23]이 (*(x->arr)+(23)과 동일함을 보여줍니다.6.5.6#8 (포인터와 정수를 더하면)은 이렇게 말합니다.

" 포인터 피연산자와 결과가 모두 동일한 배열 개체의 요소를 가리키거나 배열 개체의 마지막 요소를 가리키면 평가에서 오버플로가 발생하지 않습니다. 그렇지 않으면 동작이 정의되지 않습니다."

이러한 이유로 x->arr[23]이 배열 내에 없으므로 동작이 정의되지 않습니다.malloc()는 배열이 이제 확장되었음을 의미하기 때문에 여전히 괜찮다고 생각할 수 있지만 엄밀하게는 그렇지 않습니다.정보적 부속서 J.2(정의되지 않은 행동의 예를 나열함)는 다음과 같은 예를 들어 보다 명확하게 설명합니다.

개체가 지정된 첨자로 액세스할 수 있는 것처럼 보이는 경우(a[4][5])(6.5.6)에서 선언된 l값 식 a[1][7]에서와 같이) 배열 첨자가 범위를 벗어납니다.

3 - 구조의 끝에 동적 배열을 할당하는 것은 꽤 일반적인 C 트릭입니다.그 대안은 구조물에 포인터를 넣은 다음 배열을 따로 할당하는 것이고, 그것을 자유롭게 하는 것도 잊지 않는 것입니다.크기가 2mb로 고정된 것은 좀 특이해 보입니다.

이것은 표준 C 트릭이며 다른 버퍼보다 더 위험하지 않습니다.

만약 여러분이 상사에게 여러분이 "아주 오래된 프로그래머"보다 더 똑똑하다는 것을 보여주려고 한다면, 이 코드는 여러분에게 해당되지 않습니다.오래된 학교가 꼭 나쁜 것만은 아닙니다."오래된 학교" 남자가 기억 관리에 대해 충분히 알고 있는 것 같습니다 ;)

1) 예, 그렇지 않습니다. 연속된 블록을 충분히 사용할 수 없으면 malloc이 실패합니다. (malloc에 실패하면 NULL 포인터가 반환됩니다.

2) 네, 그럴 겁니다.내부 메모리 할당은 해당 포인터 값으로 할당된 메모리의 양을 추적하여 모두 해제합니다.

3)이것은 약간의 언어 해킹이고, 사용법에 대해서는 조금 의심스럽습니다.여전히 버퍼 오버플로의 영향을 받기 때문에 공격자가 이를 유발할 페이로드를 찾는 데 시간이 조금 더 걸릴 수 있습니다.또한 '보호' 비용도 만만치 않습니다(요청 버퍼당 2mb 이상이 정말 필요하십니까?).당신의 상사가 그 논쟁을 좋아하지 않을 수도 있지만, 그것은 또한 매우 추합니다 :)

저는 기존의 답변이 이 문제의 본질에 도달했다고 생각하지 않습니다.그 구식 프로그래머가 이런 짓을 하고 있다고 말씀하시는군요.

typedef struct ts_request
{ 
  ts_request_buffer_header_def header; 
  char                         package[1]; 
} ts_request_def;

ts_request_buffer_def* request_buffer = 
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));

제 생각에 그가 정확히 그렇게 하고 싶어했다면, 어떤 속임수도 필요 없는 단순화된 동등한 코드로 할 수 있었기 때문입니다.

typedef struct ts_request
{ 
  ts_request_buffer_header_def header; 
  char                         package[2*1024*1024 + 1]; 
} ts_request_def;

ts_request_buffer_def* request_buffer = 
malloc(sizeof(ts_request_def));

저는 그가 실제로 하고 있는 일이 이런 것이라고 확신합니다.

typedef struct ts_request
{ 
  ts_request_buffer_header_def header; 
  char                         package[1]; // effectively package[x]
} ts_request_def;

ts_request_buffer_def* request_buffer = 
malloc( sizeof(ts_request_def) + x );

그가 이루고 싶은 것은 패키지 사이즈 x가 가변적인 요청의 할당입니다.배열의 크기를 변수로 선언하는 것은 물론 불법이므로 그는 속임수로 이 문제를 해결하고 있습니다.그가 나에게 무슨 짓을 하는지 알고 있는 것처럼 보입니다. 속임수는 훌륭하고 실용적인 크로커리 스케일의 끝을 향해 있습니다.

#3의 경우, 더 이상의 코드가 없으면 답변하기 어렵습니다.많은 일이 일어나지 않는 한 저는 아무 문제가 없다고 봅니다.제 말은, 항상 2mb의 메모리를 할당하고 싶지 않다는 뜻입니다.또한 2k만 사용하는 경우와 같이 불필요하게 작업을 수행하고 싶지 않습니다.

어떤 이유에서인지 당신이 그것을 좋아하지 않는다는 사실은 그것을 반대하거나 완전히 다시 쓰는 것을 정당화하기에 충분하지 않습니다.저는 사용법을 자세히 살펴보고, 원래 프로그래머가 무엇을 생각하고 있었는지 이해하려고 노력하고, 이 메모리를 사용하는 코드에서 버퍼 오버플로(workmad3가 지적한 바와 같이)를 자세히 찾습니다.

여러분이 발견할 수 있는 흔한 실수들이 많이 있습니다.예를 들어, malloc()이 성공했는지 확인하기 위해 코드가 확인됩니까?

이 공격(질문 3)은 실제로 귀사의 이러한 구조에 대한 인터페이스에 달려 있습니다.상황에 따라 이 할당은 의미가 있을 수 있으며, 추가 정보 없이는 안전한지 여부를 말할 수 없습니다.
그러나 구조보다 메모리를 더 크게 할당하는 문제를 의미한다면, 이것은 결코 나쁜 C 설계가 아닙니다. (저는 그 오래된 학교라고 말하지도 않을 것입니다.. ;)
여기서 마지막 참고 사항 - char[1]을 가지는 경우의 요점은 종료 NULL이 항상 선언된 구조로 유지된다는 것이며, 이는 버퍼에 2 * 1024 * 1024 문자가 있을 수 있으며, NULL을 "+1"로 설명할 필요가 없습니다.작은 업적처럼 보일 수도 있지만, 저는 단지 지적하고 싶었습니다.

저는 이 패턴을 자주 보고 사용했습니다.

이것의 장점은 메모리 관리를 단순화하여 메모리 유출의 위험을 방지하는 것입니다.몰로크 블록만 풀어주면 됩니다.보조 완충기가 있으면 두 개는 무료입니다.그러나 구조물을 삭제할 때 수행할 보조 버퍼로 전환하거나 추가 작업을 추가하는 등의 동작을 항상 변경할 수 있도록 이 작업을 캡슐화하기 위해 destructor 함수를 정의하고 사용해야 합니다.

또한 어레이 요소에 대한 액세스는 약간 더 효율적이지만 현대 컴퓨터에서는 그 중요성이 점점 줄어듭니다.

또한 메모리 정렬이 매우 빈번하게 다른 컴파일러를 사용하여 구조를 변경하는 경우 코드가 올바르게 작동합니다.

제가 볼 수 있는 유일한 잠재적인 문제는 컴파일러가 구성원 변수의 저장 순서를 순열하는 것입니다. 왜냐하면 이 방법은 패키지 필드가 저장에 마지막으로 남아 있어야 하기 때문입니다.C 기준이 순열을 금지하는지 모르겠습니다.

또한 할당된 버퍼의 크기는 필요한 것보다 더 클 가능성이 크며, 추가 패딩 바이트가 있는 경우 최소 1바이트가 추가됩니다.

예. malloc는 단일 포인터만 반환합니다. 요청자에게 요청을 충족하기 위해 여러 개의 불연속 블록을 할당했다고 어떻게 말할 수 있습니까?

일반적인 것은 아니지만 윈도우 API는 그러한 용도로 가득 차 있기 때문에 표준 관행이라고 할 수도 있습니다.

예를 들어 매우 일반적인 비트맵 헤더 구조를 확인합니다.

http://msdn.microsoft.com/en-us/library/aa921550.aspx

마지막 RBG 쿼드는 정확히 이 기술에 따라 달라지는 1 크기의 배열입니다.

이 일반적인 C 트릭은 이 StackOverflow 질문에서도 설명됩니다(솔라리스에서 다른 구조의 정의를 설명해 줄 수 있는 사람이 있습니까?).

세 번째 질문에 대한 답변입니다.

free는 항상 한 번에 할당된 모든 메모리를 해제합니다.

int* i = (int*) malloc(1024*2);

free(i+1024); // gives error because the pointer 'i' is offset

free(i); // releases all the 2KB memory

1번과 2번 문제의 답은 '예'입니다.

추함(즉, 질문 3)에 대해 프로그래머는 할당된 메모리로 무엇을 하려고 합니까?

여기서 깨달아야 할 것은malloc이 경우 계산이 수행되지 않습니다.

malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));

와 같습니다.

  int sz = sizeof(ts_request_def) + (2 * 1024 * 1024);
   malloc(sz);

메모리 2개의 청크를 할당하고 있다고 생각할 수 있습니다. 그리고 그들은 "구조", "일부 버퍼"입니다.그러나 malloc는 그것을 전혀 보지 못합니다.

언급URL : https://stackoverflow.com/questions/625270/does-malloc-allocate-a-contiguous-block-of-memory