표준 라이브러리를 사용하여 정렬 된 메모리를 할당하는 방법은 무엇입니까? a) 어떻게 1024 바이트의 메모리를 할당하고

방금 면접의 일환으로 테스트를 마쳤으며 Google을 참조 용으로 사용해도 한 가지 질문으로 인해 문제가 발생했습니다. StackOverflow 승무원이 무엇을 할 수 있는지 알고 싶습니다.

memset_16aligned함수에는 16 바이트 정렬 포인터가 전달되어야합니다. 그렇지 않으면 충돌이 발생합니다.

a) 어떻게 1024 바이트의 메모리를 할당하고 16 바이트 경계에 정렬합니까?
b) memset_16aligned실행 후 메모리 를 비 웁니다.

{
   void *mem;
   void *ptr;

   // answer a) here

   memset_16aligned(ptr, 0, 1024);

   // answer b) here    
}



답변

원래 답변

{
    void *mem = malloc(1024+16);
    void *ptr = ((char *)mem+16) & ~ 0x0F;
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

정답

{
    void *mem = malloc(1024+15);
    void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

요청 된 설명

첫 번째 단계는 만일을 위해 충분한 여유 공간을 할당하는 것입니다. 메모리는 16 바이트로 정렬되어야하므로 (앞의 바이트 주소는 16의 배수 여야 함을 의미 함) 16 바이트를 추가하면 충분한 공간이 확보됩니다. 처음 16 바이트 어딘가에 16 바이트 정렬 포인터가 있습니다. (주 malloc()충분히 잘위한 정렬되는 포인터를 반환하도록되어 있는 . 목적을하지만, ‘모든’의 의미는 기본 유형 같은 것들에 대해 우선적으로 – long, double, long double, long long., 및 객체에 대한 포인터와 포인터 기능이있는 경우 그래픽 시스템을 사용하는 것과 같이보다 전문적인 작업을 수행하면 나머지 시스템보다 더 엄격한 정렬이 필요할 수 있습니다.

다음 단계는 void 포인터를 char 포인터로 변환하는 것입니다. GCC에도 불구하고, void 포인터에 대해 포인터 산술을 수행해서는 안됩니다 (GCC에는이를 악용 할 경우 알려주는 경고 옵션이 있습니다). 그런 다음 시작 포인터에 16을 추가하십시오. 가정은 malloc()당신에게 엄청나게 잘못 정렬 된 포인터 : 0x800001를 반환했습니다. 16을 더하면 0x800011이됩니다. 이제 16 바이트 경계로 내림하고 싶습니다. 따라서 마지막 4 비트를 0으로 재설정하려고합니다. 0x0F에는 마지막 4 비트가 1로 설정되어 있습니다. 따라서 ~0x0F마지막 4 개를 제외한 모든 비트가 1로 설정됩니다. 그리고 0x800011로 0x800010을 제공합니다. 다른 오프셋을 반복하고 동일한 산술이 작동하는지 확인할 수 있습니다.

마지막 단계는, free()당신은 항상, 만, 복귀 : 쉽게 free()값 그 중 하나 malloc(), calloc()또는 realloc()당신에게 반환은 – 어떤 다른 재앙이다. mem그 가치를 지키기 위해 올바르게 제공 하셨습니다. 감사합니다. 무료로 릴리스합니다.

마지막으로, 시스템 malloc패키지 의 내부에 대해 알고 있다면 16 바이트로 정렬 된 데이터를 반환하거나 8 바이트로 정렬 될 수 있다고 추측 할 수 있습니다. 16 바이트로 정렬 된 경우 값을 적을 필요가 없습니다. 그러나 이것은 dodgy하고 이식 할 수 없습니다. 다른 malloc패키지는 최소 정렬이 다르므로 다른 작업을 할 때 한 가지를 가정하면 코어 덤프가 발생합니다. 광범위한 한계 내에서이 솔루션은 이식 가능합니다.

posix_memalign()정렬 된 메모리를 얻는 다른 방법으로 다른 사람이 언급 되었습니다. 모든 곳에서 사용할 수는 없지만 종종 이것을 기본으로 사용하여 구현할 수 있습니다. 정렬은 2의 거듭 제곱 인 것이 편리하다는 점에 유의하십시오. 다른 정렬은 더 지저분합니다.

한 번 더 주석-이 코드는 할당이 성공했는지 확인하지 않습니다.

개정

Windows Programmer 는 포인터에서 비트 마스크 작업을 수행 할 수 없으며 실제로 GCC (3.4.6 및 4.3.1 테스트)는 그렇게 불평한다고 지적했습니다. 따라서 기본 코드의 수정 된 버전 (기본 프로그램으로 변환)은 다음과 같습니다. 또한 지적했듯이 16 대신 15를 추가하는 자유를 얻었습니다. uintptr_tC99는 대부분의 플랫폼에서 액세스 할 수있을 정도로 오래 지속되었으므로 사용 하고 있습니다. 명령문 PRIXPTR에서 사용하지 않은 printf()경우을 사용하는 #include <stdint.h>대신 충분합니다 #include <inttypes.h>. [이 코드에는 CR이 지적한 픽스가 포함되어 있는데 , 몇 년 전 Bill K 가 처음으로 작성한 시점을 되풀이하여 지금까지 간과 할 수있었습니다.]

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

static void memset_16aligned(void *space, char byte, size_t nbytes)
{
    assert((nbytes & 0x0F) == 0);
    assert(((uintptr_t)space & 0x0F) == 0);
    memset(space, byte, nbytes);  // Not a custom implementation of memset()
}

int main(void)
{
    void *mem = malloc(1024+15);
    void *ptr = (void *)(((uintptr_t)mem+15) & ~ (uintptr_t)0x0F);
    printf("0x%08" PRIXPTR ", 0x%08" PRIXPTR "\n", (uintptr_t)mem, (uintptr_t)ptr);
    memset_16aligned(ptr, 0, 1024);
    free(mem);
    return(0);
}

그리고 여기에 조금 더 일반화 된 버전이 있습니다.이 버전은 2의 거듭 제곱 인 크기에서 작동합니다.

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

static void memset_16aligned(void *space, char byte, size_t nbytes)
{
    assert((nbytes & 0x0F) == 0);
    assert(((uintptr_t)space & 0x0F) == 0);
    memset(space, byte, nbytes);  // Not a custom implementation of memset()
}

static void test_mask(size_t align)
{
    uintptr_t mask = ~(uintptr_t)(align - 1);
    void *mem = malloc(1024+align-1);
    void *ptr = (void *)(((uintptr_t)mem+align-1) & mask);
    assert((align & (align - 1)) == 0);
    printf("0x%08" PRIXPTR ", 0x%08" PRIXPTR "\n", (uintptr_t)mem, (uintptr_t)ptr);
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

int main(void)
{
    test_mask(16);
    test_mask(32);
    test_mask(64);
    test_mask(128);
    return(0);
}

test_mask()범용 할당 함수 로 변환하려면 할당 자로부터의 단일 반환 값이 여러 사람이 답변에 표시 한 것처럼 릴리스 주소를 인코딩해야합니다.

면접관 문제

Uri 는 다음과 같이 말했습니다. 아마도 오늘 아침에 독해 문제를 겪고있을 수도 있지만 인터뷰 질문에 구체적으로 “어떻게 1024 바이트의 메모리를 할당하겠습니까?”라고 말하면 분명히 그 이상을 할당하게됩니다. 면접관의 자동 실패가 아닌가?

내 답변이 300 자 댓글에 맞지 않습니다 …

그것은 달려 있다고 생각합니다. 저를 포함한 대부분의 사람들은 “1024 바이트의 데이터를 저장할 수있는 공간과 기본 주소가 16 바이트의 배수 인 공간을 어떻게 할당하겠습니까?” 면접관이 실제로 1024 바이트를 할당하고 16 바이트를 정렬 할 수있는 방법을 의미한다면 옵션이 더 제한적입니다.

  • 분명히 한 가지 가능성은 1024 바이트를 할당 한 다음 해당 주소에 ‘정렬 처리’를 제공하는 것입니다. 이 방법의 문제점은 실제 사용 가능한 공간이 올바르게 결정되지 않는다는 것입니다 (사용 가능한 공간은 1008에서 1024 바이트 사이이지만 어떤 크기를 지정할 수있는 메커니즘이 없었기 때문에).
  • 또 다른 가능성은 전체 메모리 할당자를 작성하고 반환하는 1024 바이트 블록이 적절하게 정렬되어 있어야한다는 것입니다. 이 경우 제안 된 솔루션과 상당히 유사한 작업을 수행 할 수 있지만 할당 기 내부에 숨길 수 있습니다.

그러나 면접관이 이러한 응답 중 하나를 예상 한 경우이 솔루션이 밀접하게 관련된 질문에 답변한다는 것을 인식 한 다음 대화를 올바른 방향으로 가리 키도록 질문을 재구성 할 것으로 기대합니다. (따라서 면접관이 정말로 비참한 사람이라면 그 일을 원하지 않을 것입니다. 충분히 정확한 요구 사항에 대한 답변이 수정없이 화염에 빠지면 면접관은 일하기에 안전한 사람이 아닙니다.)

세상은 계속 움직입니다

질문 제목이 최근에 변경되었습니다. 그것은이었다 난처한 상황에 빠진 날 C 인터뷰 질문의 메모리 정렬을 해결 . 수정 된 제목 ( 표준 라이브러리 만 사용하여 정렬 된 메모리를 할당하는 방법? )에는 약간 수정 된 답변이 필요합니다.이 부록은이를 제공합니다.

C11 (ISO / IEC 9899 : 2011) 기능 추가 aligned_alloc():

7.22.3.1 aligned_alloc기능

개요

#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);

설명
aligned_alloc함수는에 의해 정렬이 지정되고에 의해 alignment크기가 지정되고 size값이 결정되지 않은 객체에 공간을 할당합니다 . 의 값은 alignment구현에 의해 지원되는 유효한 정렬이어야하고의 값은 size의 정수배 여야합니다 alignment.

반환 함수가 반환 널 포인터 또는 할당 된 공간에 대한 포인터 중 하나를.
aligned_alloc

그리고 POSIX는 posix_memalign()다음을 정의합니다 .

#include <stdlib.h>

int posix_memalign(void **memptr, size_t alignment, size_t size);

기술

posix_memalign()함수는로 size지정된 경계에 정렬 된 바이트 alignment를 할당하고에 할당 된 메모리에 대한 포인터를 반환합니다 memptr. 의 값은 alignment2의 배수입니다 sizeof(void *).

성공적으로 완료되면에 의해 지정된 값은 memptr의 배수입니다 alignment.

요청 된 공간의 크기가 0이면 동작이 구현 정의됩니다. 리턴 된 값 memptr은 널 포인터 또는 고유 포인터 여야합니다.

free()함수는 이전에 할당 한 메모리를 할당 해제해야합니다 posix_memalign().

반품 가치

성공적으로 완료되면 posix_memalign()0을 반환합니다. 그렇지 않으면 오류를 나타 내기 위해 오류 번호가 반환됩니다.

이 두 가지 중 하나 또는 둘 다를 사용하여 지금 질문에 대답 할 수 있지만 질문에 처음 대답했을 때는 POSIX 기능 만 옵션이었습니다.

배후에서 새로운 정렬 메모리 기능은 정렬을보다 쉽게 ​​강제하고 코드가 작동하지 않도록 정렬 메모리의 시작을 내부적으로 추적하는 기능을 제외하고는 질문에 설명 된 것과 거의 동일한 작업을 수행합니다. 특별히 처리해야합니다 – 사용 된 할당 함수에 의해 반환 된 메모리를 해제합니다.


답변

질문을 보는 방식에 따라 세 가지 약간 다른 답변이 있습니다.

1) 정확한 질문에 충분하면 Jonathan Leffler의 솔루션이 충분합니다. 단, 16 정렬로 반올림하려면 16 바이트가 아닌 15 바이트 만 필요합니다.

ㅏ:

/* allocate a buffer with room to add 0-15 bytes to ensure 16-alignment */
void *mem = malloc(1024+15);
ASSERT(mem); // some kind of error-handling code
/* round up to multiple of 16: add 15 and then round down by masking */
void *ptr = ((char*)mem+15) & ~ (size_t)0x0F;

비:

free(mem);

2)보다 일반적인 메모리 할당 기능을 위해, 호출자는 두 개의 포인터 (하나는 사용하고 다른 하나는 사용 가능)를 추적하지 않아도됩니다. 따라서 정렬 된 버퍼 아래에 ‘실제’버퍼에 대한 포인터를 저장합니다.

ㅏ:

void *mem = malloc(1024+15+sizeof(void*));
if (!mem) return mem;
void *ptr = ((char*)mem+sizeof(void*)+15) & ~ (size_t)0x0F;
((void**)ptr)[-1] = mem;
return ptr;

비:

if (ptr) free(((void**)ptr)[-1]);

mem에 15 바이트 만 추가 된 (1)과 달리이 코드는 구현이 malloc에서 32 바이트 정렬을 보장하는 경우 실제로 정렬을 줄일있습니다 (그렇지 않지만 이론적으로 C 구현은 32 바이트를 가질 수 있음) 정렬 된 유형). memset_16aligned를 호출하는 것은 중요하지 않지만 구조체에 메모리를 사용하면 문제가 될 수 있습니다.

구현 별 정렬 보장이 무엇인지 프로그래밍 방식으로 결정할 수있는 방법이 없기 때문에이 문제에 대한 좋은 수정 방법이 무엇인지 잘 모르겠습니다 (반환 된 버퍼가 임의의 구조체에 반드시 적합하지는 않음을 사용자에게 경고하는 것 제외). 시작시 두 개 이상의 1 바이트 버퍼를 할당 할 수 있으며 최악의 정렬은 보장 된 정렬이라고 가정합니다. 당신이 틀렸다면, 당신은 기억을 낭비합니다. 더 좋은 아이디어를 가진 사람은 그렇게 말하십시오 …

[ 추가 : ‘표준’트릭은 필수 정렬을 결정하기 위해 ‘최대 정렬 유형’의 조합을 만드는 것입니다. 최대 정렬 유형은 (C99에서) ‘ long long‘, ‘ long double‘, ‘ void *‘또는 ‘ void (*)(void)‘일 수 있습니다. 을 포함 <stdint.h>하면 아마도 ‘ intmax_t‘를 대신 사용할 수 있습니다 long long(그리고 Power 6 (AIX) 시스템에서는 intmax_t128 비트 정수 유형을 제공합니다). 해당 유니온에 대한 정렬 요구 사항은 단일 문자 다음에 유니온이있는 구조체에 포함시켜 결정할 수 있습니다.

struct alignment
{
    char     c;
    union
    {
        intmax_t      imax;
        long double   ldbl;
        void         *vptr;
        void        (*fptr)(void);
    }        u;
} align_data;
size_t align = (char *)&align_data.u.imax - &align_data.c;

그런 다음 요청 된 정렬 중 큰 align값 ( 예 : 16)과 위에서 계산 된 값을 사용합니다.

(64 비트) Solaris 10에서 결과의 기본 정렬 malloc()은 32 바이트의 배수 인 것으로 보입니다 .

]

실제로, 정렬 된 할당자는 종종 하드 와이어가 아닌 정렬에 대한 매개 변수를 사용합니다. 따라서 사용자는 관심있는 구조체의 크기 (또는 2 이상의 최소 거듭 제곱)를 전달하면 모든 것이 잘됩니다.

3) posix_memalignPOSIX의 경우 _aligned_mallocWindows에서 플랫폼이 제공하는 것을 사용하십시오 .

4) C11을 사용하는 경우 가장 깨끗하고 이식 가능하고 간결한 옵션은 aligned_alloc이 버전의 언어 사양에 도입 된 표준 라이브러리 기능을 사용하는 것 입니다.


답변

posix_memalign()물론 POSIX 플랫폼 에서도 시도해 볼 수 있습니다 .


답변

다음은 ‘반올림’부분에 대한 대체 접근 방식입니다. 가장 훌륭하게 코딩 된 솔루션은 아니지만 작업이 완료 되며이 구문 유형은 기억하기가 더 쉽습니다 (또한 2의 거듭 제곱이 아닌 정렬 값에서도 작동합니다). uintptr_t캐스트는 컴파일러를 달래 필요하다고; 포인터 산술은 나눗셈이나 곱셈을 좋아하지 않습니다.

void *mem = malloc(1024 + 15);
void *ptr = (void*) ((uintptr_t) mem + 15) / 16 * 16;
memset_16aligned(ptr, 0, 1024);
free(mem);


답변

불행히도 C99에서는 C99를 준수하는 모든 C 구현에서 이식 가능한 방식으로 정렬을 보장하기가 매우 어려워 보입니다. 왜? 포인터가 “바이트 주소”라고 보장되지 않기 때문에 플랫 메모리 모델에서는 상상할 수 있습니다. uintptr_t 의 표현 도 보장되지 않으며, 그 자체는 선택적인 유형입니다.

우리 는 간단한 바이트 주소 인 void * (및 정의에 따라 char * )에 대한 표현을 사용하는 일부 구현을 알고 있지만 C99에 의해 프로그래머에게는 불투명합니다. 구현은 set { segment , offset } 으로 포인터를 표현할 수 있는데 , 여기서 offset 은 “실제로”어떤 ​​정렬을 가질 수 있는지 알 수 있습니다. 이유는 포인터가 해시 테이블 조회 값의 형태이거나 연결된 목록 조회 값일 수도 있습니다. 범위 정보를 인코딩 할 수 있습니다.

C 표준에 대한 최근 C1X 초안에는 _Alignas 키워드가 있습니다. 약간 도움이 될 수 있습니다.

C99가 제공하는 유일한 보장은 메모리 할당 함수가 모든 객체 유형을 가리키는 포인터에 할당하기에 적합한 포인터를 반환한다는 것입니다. 객체의 정렬을 지정할 수 없으므로 잘 정의 된 이식 가능한 방식으로 정렬을 담당하는 자체 할당 기능을 구현할 수 없습니다.

이 주장에 대해 틀린 것이 좋을 것입니다.


답변

16 대 15 바이트 수 패딩 앞면에서 N의 정렬을 얻기 위해 추가해야하는 실제 숫자는 max (0, NM)입니다. 여기서 M은 메모리 할당 자의 자연 정렬입니다 (둘 다 2의 거듭 제곱 임).

할당 자의 최소 메모리 정렬은 1 바이트이므로 15 = max (0,16-1)은 보수적 인 답입니다. 그러나 메모리 할당자가 32 비트 int로 정렬 된 주소를 제공한다는 것을 알고 있다면 12를 패드로 사용할 수 있습니다.

이 예제에서는 중요하지 않지만 12K의 RAM이 내장 된 시스템에서는 모든 int가 카운트를 저장하는 것이 중요 할 수 있습니다.

실제로 가능한 모든 바이트를 저장하려고 할 때 구현하는 가장 좋은 방법은 매크로로 매크로를 사용하여 기본 메모리 정렬을 제공 할 수 있습니다. 다시 말하지만, 이것은 모든 바이트를 저장해야하는 임베디드 시스템에만 유용합니다.

아래 예에서 대부분의 시스템에서 값 1은 적합 MEMORY_ALLOCATOR_NATIVE_ALIGNMENT하지만 32 비트 정렬 할당이있는 이론적 임베디드 시스템의 경우 다음과 같은 소중한 메모리를 절약 할 수 있습니다.

#define MEMORY_ALLOCATOR_NATIVE_ALIGNMENT    4
#define ALIGN_PAD2(N,M) (((N)>(M)) ? ((N)-(M)) : 0)
#define ALIGN_PAD(N) ALIGN_PAD2((N), MEMORY_ALLOCATOR_NATIVE_ALIGNMENT)


답변

아마도 그들은 memalign에 대한 지식에 만족했을 것 입니까 ? Jonathan Leffler가 지적했듯이, 알아야 할 두 가지 새로운 기능이 있습니다.

플로린이 날 이겼어 그러나 내가 링크 한 매뉴얼 페이지를 읽으면 이전 포스터에서 제공 한 예제를 이해할 것입니다.