배열 또는 Malloc?


13

내 응용 프로그램에서 다음 코드를 사용하고 있으며 정상적으로 작동합니다. 그러나 malloc으로 만들거나 그대로 두는 것이 더 좋은지 궁금합니다.

function (int len)
{
char result [len] = some chars;
send result over network
}

2
코드가 내장되지 않은 환경을 대상으로한다는 가정입니까?
tehnyit

답변:


28

주요 차이점은 VLA (가변 길이 배열)가 할당 실패를 감지하는 메커니즘을 제공하지 않는다는 것입니다.

선언하면

char result[len];

그리고 len가능한 스택 공간의 양이 프로그램의 행동이 정의되지 초과합니다. 할당의 성공 여부를 미리 결정하거나 성공 여부를 결정한 후에는 언어 메커니즘이 없습니다.

반면에 다음과 같이 쓰면 :

char *result = malloc(len);
if (result == NULL) {
    /* allocation failed, abort or take corrective action */
}

그런 다음 실패를 정상적으로 처리하거나 최소한 실패 후에도 프로그램이 계속 실행되지 않도록 보장 할 수 있습니다.

(주로 Linux 시스템에서는 malloc()사용 가능한 해당 스토리지가없는 경우에도 주소 공간 청크를 할당 할 수 있습니다. 나중에 해당 공간을 사용하려고 시도하면 OOM Killer 가 호출 될 수 있습니다 . 그러나 malloc()여전히 실패를 확인하는 것이 좋습니다.)

또 다른 문제는, 많은 시스템에 더 많은 공간 (가능성이 있다는 것입니다 많이 사용할 더 많은 공간) malloc()블라스와 같은 자동 객체보다가.

필립의 답변에서 이미 언급했듯이 VLA는 C99에 추가되었습니다 (특히 Microsoft는이를 지원하지 않습니다).

그리고 VLA는 C11에서 선택 사항이되었습니다. 아마도 대부분의 C11 컴파일러가이를 지원할 것이지만 믿을 수는 없습니다.


14

C99에서 가변 길이 자동 배열이 C에 도입되었습니다.

이전 표준과의 하위 호환성에 대해 걱정하지 않는 한 괜찮습니다.

일반적으로 작동하면 만지지 마십시오. 미리 최적화하지 마십시오. 특별한 기능을 추가하거나 일을하는 영리한 방법을 추가하는 것에 대해 걱정하지 마십시오. 자주 사용하지 않기 때문입니다. 간단하게 유지하십시오.


7
나는 "만약 그것이 작동한다면 만지지 말라"는 말에 동의하지 않아야한다. 일부 "코드"가 작동한다고 잘못 판단하면 "작동"하는 일부 코드의 문제를 해결할 수 있습니다. 일부 코드는 현재 작동한다는 믿음을 잠정적으로 받아 들여야합니다.
Bruce Ediger

2
"작동"버전이 나올 때까지 만지지 마십시오.
H_7

8

컴파일러가 가변 길이 배열을 지원하면 len엄청나게 큰 일부 시스템에서 스택이 오버플로되는 위험이 있습니다. 당신은 확실히 알고있는 경우 len특정 숫자보다 큰 될 수 없습니다, 당신은 당신의 스택, 심지어 최대 길이 오버 플로우에가는대로 코드를 떠나지 않을 것을 알고; 그렇지 않으면 mallocand로 다시 작성하십시오 free.


c99가 아닌 함수에서 이것에 대해 어떻습니까 (char []) {char result [sizeof (char)] = some chars; 네트워크를 통해 결과 전송}
Dev Bag

@DevBag char result [sizeof(char)]는 크기 배열 1이므로 sizeof(char)1과 같으므로 할당이 잘립니다 some chars.
dasblinkenlight 2016

죄송합니다. 이런 식으로 function (char str []) {char result [sizeof (str)] = some chars; 네트워크를 통해 결과 전송}
Dev Bag

4
@DevBag 이것은 작동하지 않을 것입니다- str 포인터로 쇠퇴 하므로 sizeof시스템의 포인터 크기에 따라 4-8 개가됩니다.
dasblinkenlight

2
가변 길이 배열없이 C 버전을 사용하는 char* result = alloca(len);경우 스택에 할당 할 수 있습니다 . 그것은 동일한 기본 효과 (그리고 동일한 기본 문제)를 가지고 있습니다
로봇 Gort

6

메모리 조각화, 매달려있는 포인터 등이없는 런타임 할당 된 배열을 가질 수 있다는 아이디어가 마음에 듭니다. 그러나 다른 사람들은이 런타임 할당이 자동으로 실패 할 수 있다고 지적했습니다. 그래서 Cygwin bash 환경에서 gcc 4.5.3을 사용하여 이것을 시도했습니다.

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

void testit (unsigned long len)
{
    char result [len*2];
    char marker[100];

    memset(marker, 0, sizeof(marker));
    printf("result's size: %lu\n", sizeof(result));
    strcpy(result, "this is a test that should overflow if no allocation");
    printf("marker's contents: '%s'\n", marker);
}

int main(int argc, char *argv[])
{
    testit(100);
    testit((unsigned long)-1);  // probably too big
}

결과는 다음과 같습니다.

$ ./a.exe
result's size: 200
marker's contents: ''
result's size: 4294967294
marker's contents: 'should overflow if no allocation'

두 번째 호출에서 지나친 길이가 너무 길어서 오류가 발생했습니다 (marker []로 넘침). 그렇다고 이런 종류의 검사가 바보가 아니거나 (영리 할 수 ​​있음) C99의 표준을 충족한다는 의미는 아니지만 그러한 우려가있는 경우 도움이 될 수 있습니다.

평소처럼 YMMV.


1
+1 이것은 매우 유용합니다 : 3
Kokizzu

사람들이 주장하는 내용과 함께 코드를 작성하는 것이 항상 좋습니다! 감사합니다 ^ _ ^
무사 알 - hassy

3

일반적으로 스택은 데이터를 저장하기에 가장 쉽고 가장 좋은 장소입니다.

나는 당신이 기대하는 가장 큰 배열을 할당함으로써 VLA의 문제를 피할 것입니다.

그러나 힙이 가장 좋고 malloc을 어지럽히는 것이 노력할 가치가있는 경우가 있습니다.

  1. 크지 만 가변적 인 양의 데이터 사용자 환경은 임베디드 시스템의 경우 1K 이상, 엔터프라이즈 서버의 경우 10MB 이상입니다.
  2. 루틴을 종료 한 후 데이터를 유지하려는 경우 (예 : 데이터에 대한 포인터를 반환하는 경우) 사용
  3. 정적 포인터와 malloc ()의 조합은 일반적으로 큰 정적 배열을 정의하는 것보다 낫습니다.

3

임베디드 프로그래밍에서, malloc과 free 연산이 빈번한 경우 malloc 대신 항상 정적 배열을 사용합니다. 임베디드 시스템의 메모리 관리 부족으로 인해 빈번한 할당 및 무료 작업으로 인해 메모리 조각이 발생할 수 있습니다. 그러나 배열의 최대 크기 정의 및 정적 로컬 배열 사용과 같은 까다로운 방법을 사용해야합니다.

응용 프로그램이 Linux 또는 Windows에서 실행중인 경우 배열 또는 malloc을 사용하더라도 상관 없습니다. 요점은 날짜 구조와 코드 논리를 사용하는 위치에 있습니다.


1

아무도 언급하지 않은 것은 VLA 할당이 스택 포인터를 조정하는 경우 (GCC에서) 가변 길이 배열 옵션이 malloc / free보다 훨씬 빠를 것입니다.

따라서이 함수가 자주 호출되는 기능인 경우 (물론 프로파일 링에 의해 결정됨) VLA는 좋은 최적화 옵션입니다.


1
스택 공간 부족 상황으로 당신을 밀어 넣을 때까지 그것은 좋을 것 같습니다. 게다가 실제로 스택 제한에 도달하는 코드가 아닐 수도 있습니다 . 라이브러리 또는 시스템 호출 (또는 인터럽트)에서 물릴 수 있습니다.
Donal Fellows

@Donal Performance는 항상 속도와 메모리의 균형을 유지합니다. 몇 메가 바이트의 배열을 할당하는 라운드를 진행하려는 경우 함수가 재귀 적이 지 않는 한 몇 킬로바이트에도 포인트가 있습니다. 최적화입니다.
JeremyP

1

이것은 내가 도움이 될 수있는 문제에 사용하는 매우 일반적인 C 솔루션입니다. VLA와 달리 병리학 적 사례에서 스택 오버플로의 실제 위험에 직면하지 않습니다.

/// Used for frequent allocations where the common case generally allocates
/// a small amount of memory, at which point a heap allocation can be
/// avoided, but rare cases also need to be handled which may allocate a
/// substantial amount. Note that this structure is not safe to copy as
/// it could potentially invalidate the 'data' pointer. Its primary use
/// is just to allow the stack to be used in common cases.
struct FastMem
{
    /// Stores raw bytes for fast access.
    char fast_mem[512];

    /// Points to 'fast_mem' if the data fits. Otherwise, it will point to a
    /// dynamically allocated memory address.
    void* data;
};

/// @return A pointer to a newly allocated memory block of the specified size.
/// If the memory fits in the specified fast memory structure, it will use that
/// instead of the heap.
void* fm_malloc(struct FastMem* mem, int size)
{
    // Utilize the stack if the memory fits, otherwise malloc.
    mem->data = (size < sizeof mem->fast_mem) ? mem->fast_mem: malloc(size);
    return mem->data;
}

/// Frees the specified memory block if it has been allocated on the heap.
void fm_free(struct FastMem* mem)
{
    // Free the memory if it was allocated dynamically with 'malloc'.
    if (mem->data != mem->fast_mem)
        free(mem->data);
    mem->data = 0;
}

귀하의 경우에 사용하려면 :

struct FastMem fm;

// `result` will be allocated on the stack if 'len <= 512'.
char* result = fm_malloc(&fm, len);

// send result over network.
...

// this function will only do a heap deallocation if 'len > 512'.
fm_free(&fm, result);

위의 경우 문자열이 512 바이트 이하인 경우 스택을 사용합니다. 그렇지 않으면 힙 할당을 사용합니다. 예를 들어, 시간의 99 %가 문자열이 512 바이트 이하인 경우 유용 할 수 있습니다. 그러나 때로는 문자열이 32 킬로바이트 인 곳에서 사용자가 키보드에서 잠들거나 그와 비슷한 것을 처리해야 할 수도있는 미친 이국적인 사례가 있다고 가정 해 봅시다. 이를 통해 두 상황을 모두 문제없이 처리 할 수 ​​있습니다.

I는 제조에 사용되는 실제 버전은 자체 버전 갖는다 realloccalloc동일한 개념을 기반으로 표준 순응 C ++ 데이터 구조 등뿐만 아니라,하지만 개념을 설명하기 위해 필요한 최소한의 추출 하였다.

복사하는 것이 위험하다는 경고가 있으며, 그것을 통해 할당 된 포인터를 반환해서는 안됩니다 ( FastMem인스턴스가 파괴 되면 무효화 될 수 있음 ). 로컬 함수의 범위 내에서 항상 스택 / VLA를 사용하려는 유혹을받는 간단한 경우에 사용하기위한 것입니다. 그렇지 않으면 드문 경우로 버퍼 / 스택 오버플로가 발생할 수 있습니다. 범용 할당자가 아니므로 그대로 사용해서는 안됩니다.

실제로 C89를 사용하는 레거시 코드베이스의 상황에 대한 응답으로 이전 팀은 사용자가 2047자를 초과하는 이름을 가진 항목의 이름을 지정할 수 없을 것이라고 생각했습니다 (키보드에서 잠들었을 수도 있음) ). 내 동료들은 실제로 다양한 장소에 할당 된 배열의 크기를 16,384로 늘리려 고 노력했습니다.이 시점에서 나는 어리석은 것으로 생각하고 버퍼 오버플로의 위험을 줄이면서 스택 오버플로의 위험을 높이는 것으로 생각했습니다. 이것은 단지 몇 줄의 코드를 추가함으로써 이러한 경우를 해결하기 위해 플러그인하기 매우 쉬운 솔루션을 제공했습니다. 이로 인해 일반적인 경우를 매우 효율적으로 처리 할 수 ​​있었으며 소프트웨어를 충돌시키는 힙을 요구하는 드문 경우가 거의없이 스택을 계속 사용할 수있었습니다. 그러나 나는 VLA는 여전히 스택 오버플로로부터 우리를 보호 할 수 없기 때문에 C99 이후에도 유용하다는 것을 알게되었습니다. 이것은 작은 할당 요청을 위해 여전히 스택에서 풀링 할 수 있습니다.


1

호출 스택은 항상 제한됩니다. Linux 또는 Windows와 같은 주류 OS에서 한도는 몇 메가 바이트 (및이를 변경할 수있는 방법을 찾을 수 있음)입니다. 일부 멀티 스레드 응용 프로그램의 경우 스레드가 더 작은 스택으로 생성 될 수 있으므로 더 낮아질 수 있습니다. 임베디드 시스템에서는 몇 킬로바이트만큼 작을 수 있습니다. 일반적으로 몇 킬로바이트보다 큰 통화 프레임을 피하는 것이 좋습니다.

따라서 VLA 를 사용하는 len것이 충분히 작은 경우 (최대 수십만)에 해당하는 경우에만 의미가 있습니다 . 그렇지 않으면 스택 오버플 로가 발생하며 정의되지 않은 동작 의 경우 매우 무서운 상황입니다.

그러나 수동 C 동적 메모리 할당 (예 : calloc또는 malloc&free )을 사용하면 단점도 있습니다.

  • 실패 할 수 있으며 항상 실패를 테스트 해야합니다 (예 : calloc또는 malloc리턴 NULL).

  • VLA 할당에 성공 malloc하려면 몇 나노초가 걸리고, 성공 하려면 몇 마이크로 초 (좋은 경우 마이크로 초의 몇 분의 1 초) 또는 더 많이 ( 쓰 레싱 과 관련된 병리학적인 경우 훨씬 더) 가 필요할 수 있습니다.

  • 코딩하기가 훨씬 어렵습니다. free뾰족한 영역이 더 이상 사용되지 않는 경우에만 가능 합니다. 귀하의 경우에 당신은 모두를 호출 할 수 callocfree같은 루틴입니다.

당신은 대부분의 시간이 알고 있다면 result (매우 가난한 이름, 당신은 결코 의 주소를 반환해야합니다 자동 변수 VLA를, 내가 사용하므로 buf대신 result아래)이다 작은 그럴 수있어 특별한 경우, 예를 들어,

char tinybuf[256];
char *buf = (len<sizeof(tinybuf))?tinybuf:malloc(len);
if (!buf) { perror("malloc"); exit(EXIT_FAILURE); };
fill_buffer(buf, len);
send_buffer_on_network(buf, len);
if (buf != tinybuf) 
  free(buf);

그러나 위의 코드는 읽기 쉽지 않으며 아마도 조기 최적화 일 것입니다. 그러나 순수한 VLA 솔루션보다 강력합니다.

추신. 일부 시스템 (예 : 일부 Linux 배포판은 기본적으로 활성화되어 있음)에는 메모리 초과 커밋이 있습니다 ( 메모리malloc 가 충분하지 않더라도 포인터 를 제공함). 이것은 내가 싫어하는 기능이며 일반적으로 Linux 시스템에서 비활성화합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.