표준 라이브러리를 사용하여 정렬 된 메모리를 할당하는 방법은 무엇입니까?


421

방금 면접의 일환으로 테스트를 마쳤으며 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    
}

89
hmmm ... 장기적인 코드 실행 성을 위해 "memset_16을 작성한 사람을 해고하여 수정하거나 고유 한 경계 조건이 없도록 수정하거나 교체하십시오"
Steven A. Lowe

29
"특별한 메모리 정렬이 왜 필요한가?" 그러나 그럴만한 이유가있을 수 있습니다.이 경우 memset_16aligned ()는 128 비트 정수를 사용할 수 있으며 메모리가 정렬 된 것으로 알려진 경우 더 쉽습니다. 기타
Jonathan Leffler

5
memset을 작성한 사람은 내부 루프를 지우기 위해 내부 16 바이트 정렬과 작은 데이터 프롤로그 / 에필로그를 사용하여 정렬되지 않은 끝을 정리할 수 있습니다. 코더가 추가 메모리 포인터를 처리하는 것보다 훨씬 쉽습니다.
Adisak

8
누군가 16 바이트 경계에 데이터를 정렬하려는 이유는 무엇입니까? 128 비트 SSE 레지스터에로드 할 수 있습니다. 나는 (최신의) 정렬되지 않은 mov (예를 들어, movupd, lddqu)가 느리거나 SSE2 / 3가없는 프로세서를 목표로하고 있다고 생각합니다.

11
주소를 정렬하면 캐시 사용이 최적화되고 다른 수준의 캐시와 RAM (대부분의 일반적인 작업 부하)간에 더 높은 대역폭이 사용됩니다. 여기를 참조하십시오 stackoverflow.com/questions/381244/purpose-of-memory-alignment
Deepthought

답변:


585

원래 답변

{
    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 기능 만 옵션이었습니다.

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


13
그리고 C ++로 녹슨 있지만 ~ 0x0F가 포인터의 크기로 올바르게 확장된다는 것을 정말로 신뢰하지 않습니다. 그렇지 않으면 포인터의 가장 중요한 부분을 가리기 때문에 모든 지옥이 풀릴 것입니다. 그래도 나는 틀릴 수 있습니다.
Bill K

66
BTW '+15'는 '+16'뿐만 아니라 작동하지만이 상황에는 실질적인 영향이 없습니다.
Menkboy

15
Menkboy와 Greg의 '+ 15'의견은 정확하지만 malloc ()은 거의 16까지 올릴 것입니다. +16을 사용하는 것은 설명하기가 쉽지 않습니다. 일반화 된 솔루션은 어리석지 만 가능합니다.
Jonathan Leffler

6
@ Aerovistae : 약간 까다로운 질문이며, 임의의 숫자 (실제로는 메모리 할당자가 반환 한 주소)를 특정 요구 사항 (16의 배수)과 일치시키는 방법에 대한 이해에 달려 있습니다. 53을 가장 가까운 16의 배수로 반올림했다면 어떻게 할 것입니까? 프로세스는 주소와 크게 다르지 않습니다. 그것은 당신이 일반적으로 다루는 숫자가 더 크다는 것입니다. 잊지 말고, 인터뷰 질문은 당신이 어떻게 생각하는지 알아 내고, 당신이 답을 알고 있는지 알아 내지 말아야합니다.
Jonathan Leffler

3
@akristmann : <inttypes.h>C99에서 사용할 수 있다면 원본 코드는 정확합니다 (적어도 형식 문자열에 대해서는 — 캐스트와 함께 값을 전달해야합니다 (uintptr_t)mem, (uintptr_t)ptr). 형식 문자열은 문자열 연결에 의존하며 PRIXPTR 매크로는 값의 printf()16 진 출력에 대한 올바른 길이 및 유형 지정자입니다 uintptr_t. 대안은 사용하는 %p것이지만 그 출력은 플랫폼에 따라 다르며 (일부는 선행을 추가하고 0x대부분은 그렇지 않습니다) 일반적으로 소문자 16 진수로 작성됩니다. 내가 쓴 것은 여러 플랫폼에서 균일합니다.
Jonathan Leffler 2014 년

58

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

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이 버전의 언어 사양에 도입 된 표준 라이브러리 기능을 사용하는 것 입니다.


1
동의합니다-질문의 의도는 메모리 블록을 해제하는 코드가 '요리 된 '16 바이트 정렬 포인터에만 액세스 할 수 있다는 것입니다.
Michael Burr

1
일반적인 해결책-당신이 맞습니다. 그러나 질문의 ​​코드 템플릿은 두 가지를 모두 명확하게 보여줍니다.
Jonathan Leffler

1
물론, 좋은 면접에서 당신은 당신의 답변을하고, 면접관이 내 답변을보고 싶다면 그들은 질문을 바꿉니다.
Steve Jessop

1
ASSERT(mem);할당 결과를 확인하는 데 사용 하는 것에 반대합니다 . assert프로그래밍 오류를 포착하고 런타임 리소스가 부족하지 않습니다.
hlovdal

4
a char *및 a 와 함께 이진 &를 사용 size_t하면 오류가 발생합니다. 와 같은 것을 사용해야합니다 uintptr_t.
Marko


20

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

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

2
일반적으로 '부호없는 long long'인 경우 uintptr_t도 명시 적으로 정의되어있어 데이터 포인터 (void *)를 보유 할만 큼 충분히 큽니다. 그러나 어떤 이유로 든 2의 거듭 제곱이 아닌 정렬이 필요한 경우 솔루션에 실제로 장점이 있습니다.
Jonathan Leffler

@Andrew : 이 유형의 구문에 대해 찬성 하는 것은 조금 더 기억하기 쉽습니다 (또한 2의 거듭 제곱이 아닌 정렬 값에도 작동합니다) .
legends2k

19

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

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

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

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

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


C11이 aligned_alloc()있습니다. (C ++ 11 / 14 / 1z에는 아직 없습니다). _Alignas()C ++ alignas()은 동적 할당을 위해 아무것도하지 않고 자동 및 정적 저장소 (또는 구조체 레이아웃)에만 적용합니다.
Peter Cordes

15

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)

8

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

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


1
주의 현재 (2 월 (2016)) 버전의 것을 참조 페이지 는 "말한다 memalign기능은 무효이며, aligned_alloc또는 posix_memalign대신 사용해야합니다." 2008 년 10 월에 무슨 말을했는지 모르겠지만 aligned_alloc()C11에 추가 된 내용은 언급하지 않았을 것입니다 .
Jonathan Leffler 2019

5

우리는 항상 정렬에주의를 기울여야하는 벡터화 된 OS X / iOS 라이브러리 인 Accelerate.framework에 대해 이런 종류의 작업을 항상 수행합니다. 위에서 언급하지 않은 옵션 중 하나 또는 두 가지가 있습니다.

이와 같은 작은 배열의 가장 빠른 방법은 스택에 붙이는 것입니다. GCC / clang 사용시 :

 void my_func( void )
 {
     uint8_t array[1024] __attribute__ ((aligned(16)));
     ...
 }

free ()가 필요하지 않습니다. 이는 일반적으로 스택 포인터에서 1024를 뺀 다음 -alignment로 스택 포인터를 뺀 명령입니다. 아마도 요청자는 어레이의 수명이 스택을 초과했거나 재귀가 작동 중이거나 스택 공간이 심각한 프리미엄이기 때문에 힙에 데이터가 필요했을 것입니다.

OS X / iOS에서 malloc / calloc / etc에 대한 모든 호출. 항상 16 바이트로 정렬됩니다. 예를 들어 AVX에 대해 정렬 된 32 바이트가 필요한 경우 posix_memalign을 사용할 수 있습니다.

void *buf = NULL;
int err = posix_memalign( &buf, 32 /*alignment*/, 1024 /*size*/);
if( err )
   RunInCirclesWaivingArmsWildly();
...
free(buf);

일부 사람들은 비슷하게 작동하는 C ++ 인터페이스를 언급했습니다.

페이지가 2의 거듭 제곱으로 정렬되므로 페이지 정렬 버퍼도 16 바이트로 정렬됩니다. 따라서 mmap () 및 valloc () 및 기타 유사한 인터페이스도 옵션입니다. mmap ()은 원하는 경우 버퍼에 0이 아닌 값으로 미리 초기화 할 수 있다는 이점이 있습니다. 이들은 페이지 정렬 된 크기를 갖기 때문에 최소 할당량을 얻지 못하며 처음 만질 때 VM 오류가 발생할 수 있습니다.

치즈 : 가드 malloc 또는 이와 유사한 기능을 켭니다. VM이 오버런을 포착하는 데 사용되고 경계가 페이지 경계에 있기 때문에 이와 같은 크기가 n * 16 바이트 인 버퍼는 n * 16 바이트로 정렬됩니다.

일부 Accelerate.framework 함수는 사용자 제공 임시 버퍼를 사용하여 스크래치 공간으로 사용합니다. 여기서 우리에게 전달 된 버퍼가 잘못 정렬되어 있고 사용자가 적극적으로 삶을 힘들게하려고한다고 가정해야합니다. (테스트 케이스는 임시 버퍼 바로 앞뒤에 가드 페이지를 붙여서 스프라이트에 밑줄을 긋습니다.) 여기서 16 바이트 정렬 세그먼트를 보장하는 데 필요한 최소 크기를 반환 한 다음 버퍼를 수동으로 정렬합니다. 이 크기는 desired_size + alignment-1입니다. 따라서이 경우 1024 + 16-1 = 1039 바이트입니다. 그런 다음 다음과 같이 정렬하십시오.

#include <stdint.h>
void My_func( uint8_t *tempBuf, ... )
{
    uint8_t *alignedBuf = (uint8_t*) 
                          (((uintptr_t) tempBuf + ((uintptr_t)alignment-1)) 
                                        & -((uintptr_t) alignment));
    ...
}

alignment-1을 추가하면 포인터가 첫 번째 정렬 된 주소를지나 이동 한 다음 -alignment와 함께 AND (예 : alignment = 16의 경우 0xfff ... ff0)가 정렬 된 주소로 다시 이동합니다.

다른 게시물에서 설명했듯이 16 바이트 정렬 보장이없는 다른 운영 체제에서는 더 큰 크기의 malloc을 호출하고 나중에 free ()에 대한 포인터를 따로 설정 한 다음 바로 위에 설명 된대로 정렬하고 정렬 된 포인터를 사용할 수 있습니다 임시 버퍼 케이스에 대해 설명했습니다.

align_memset에 관해서는, 이것은 다소 바보입니다. 정렬 된 주소에 도달하기 위해 최대 15 바이트 만 루핑 한 다음 마지막에 가능한 정리 코드를 사용하여 정렬 된 저장소로 진행해야합니다. 정렬 된 영역과 겹치는 정렬되지 않은 저장소 (길이가 벡터의 길이 이상인 경우) 또는 movmaskdqu와 같은 것을 사용하여 벡터 코드에서 정리 비트를 수행 할 수도 있습니다. 누군가가 게으르고 있습니다. 그러나 면접관이 stdint.h, 비트 연산자 및 메모리 기본 사항에 익숙한 지 알고 싶을 경우 합리적인 면접 질문 일 수 있습니다.


5

아무도 공식적인 포인터를 공식적인 형식으로 변환하는 것은 정의되지 않은 동작이기 때문에 표준 C99에서 요청한 것을 수행하는 것이 불가능하다는 Shao답변에 아무도 투표하지 않은 것에 놀랐습니다 . ( uintptr_t<->의 변환을 허용하는 표준을 void*제외하고 표준은 uintptr_t값을 조작 한 다음 다시 변환하는 것을 허용하지 않는 것 같습니다 .)


uintptr_t 유형이 존재하거나 해당 비트가 기본 포인터의 비트와 관련이있을 필요는 없습니다. 스토리지를 과도하게 할당하려면 포인터를 unsigned char* myptr; 그런 다음`mptr + = (16- (uintptr_t) my_ptr) & 0x0F를 계산하면 my_ptr을 정의하는 모든 구현에서 동작이 정의되지만 결과 포인터가 정렬되는지 여부는 uintptr_t 비트와 주소 간의 매핑에 따라 달라집니다.
supercat

3

memalign, Aligned-Memory-Blocks 를 사용하면 문제를 해결할 수 있습니다.


주의 현재 (2 월 (2016)) 버전의 것을 참조 페이지 는 "말한다 memalign기능은 무효이며, aligned_alloc또는 posix_memalign대신 사용해야합니다." 2010 년 10 월에 무슨 말을했는지 모르겠습니다.
Jonathan Leffler

3

이 질문을 읽을 때 머리에 떠오른 첫 번째 것은 정렬 된 구조체를 정의하고 인스턴스화 한 다음 가리 키도록하는 것입니다.

아무도 이것을 제안하지 않아서 내가 놓친 근본적인 이유가 있습니까?

참고로, char 배열 (시스템의 char이 8 비트 (즉 1 바이트)이라고 가정)을 사용했기 때문에 필자가 필요하지 않다는 것을 __attribute__((packed))알지 못하지만 (잘못하면 수정) 어떠한 방식으로.

이것은 내가 시도한 두 시스템에서 작동하지만 코드의 효능에 대해 잘못된 긍정을주는 것을 모르는 컴파일러 최적화가있을 수 있습니다. gcc 4.9.2OSX와 gcc 5.2.1우분투 에서 사용 했습니다 .

#include <stdio.h>
#include <stdlib.h>

int main ()
{

   void *mem;

   void *ptr;

   // answer a) here
   struct __attribute__((packed)) s_CozyMem {
       char acSpace[16];
   };

   mem = malloc(sizeof(struct s_CozyMem));
   ptr = mem;

   // memset_16aligned(ptr, 0, 1024);

   // Check if it's aligned
   if(((unsigned long)ptr & 15) == 0) printf("Aligned to 16 bytes.\n");
   else printf("Rubbish.\n");

   // answer b) here
   free(mem);

   return 1;
}

1

MacOS X 전용 :

  1. malloc으로 할당 된 모든 포인터는 16 바이트로 정렬됩니다.
  2. C11이 지원되므로 alignment_malloc (16, size) 만 호출하면됩니다.

  3. MacOS X는 부팅시 memset, memcpy 및 memmove를 위해 개별 프로세서에 최적화 된 코드를 선택하며,이 코드는 들어 본 적이없는 트릭을 사용하여 빠르게 처리합니다. 99 % 확률로 손으로 쓴 memset16보다 memset이 더 빨리 실행되므로 전체 질문이 무의미합니다.

100 % 휴대용 솔루션을 원한다면 C11 이전에는 솔루션이 없습니다. 포인터의 정렬을 테스트하는 이식 가능한 방법이 없기 때문입니다. 100 % 휴대 할 필요가 없다면

char* p = malloc (size + 15);
p += (- (unsigned int) p) % 16;

이것은 포인터를 부호없는 int로 변환 할 때 포인터의 정렬이 가장 낮은 비트에 저장되어 있다고 가정합니다. unsigned int로 변환하면 정보가 손실되고 구현이 정의되지만 결과를 포인터로 다시 변환하지 않기 때문에 문제가되지 않습니다.

끔찍한 부분은 물론 원래 포인터를 무료로 호출하기 위해 어딘가에 저장해야한다는 것입니다. 그래서 나는이 디자인의 지혜를 정말로 의심 할 것입니다.


1
aligned_mallocOS X에서 어디 에서 찾을 수 있습니까 ? Xcode 6.1을 사용하고 있으며 iOS SDK의 어느 곳에도 정의되어 있지 않으며에서도 선언되어 있지 않습니다 /usr/include/*.
Todd Lehman

El Capitan의 XCode 7.2 용 Ditto (Mac OS X 10.11.3). C11 함수는 어쨌든 aligned_alloc()선언되었지만 선언되지 않았습니다. GCC 5.3.0에서, 나는 흥미로운 메시지를 얻을 alig.c:7:15: error: incompatible implicit declaration of built-in function ‘aligned_alloc’ [-Werror]하고 alig.c:7:15: note: include ‘<stdlib.h>’ or provide a declaration of ‘aligned_alloc’. 코드에는 실제로 포함 <stdlib.h>되었지만 오류 메시지는 변경 되지 -std=c11않았습니다 -std=gnu11.
Jonathan Leffler 2019

0

16 바이트를 추가 한 다음 포인터 아래에 (16-mod)를 추가하여 원래 ptr을 16 비트로 정렬 할 수 있습니다.

main(){
void *mem1 = malloc(1024+16);
void *mem = ((char*)mem1)+1; // force misalign ( my computer always aligns)
printf ( " ptr = %p \n ", mem );
void *ptr = ((long)mem+16) & ~ 0x0F;
printf ( " aligned ptr = %p \n ", ptr );

printf (" ptr after adding diff mod %p (same as above ) ", (long)mem1 + (16 -((long)mem1%16)) );


free(mem1);
}

0

단일 바이트를 낭비 할 수없는 제약 조건이있는 경우이 솔루션이 작동합니다. 참고 : 무한대로 실행될 수있는 경우가 있습니다 : D

   void *mem;  
   void *ptr;
try:
   mem =  malloc(1024);  
   if (mem % 16 != 0) {  
       free(mem);  
       goto try;
   }  
   ptr = mem;  
   memset_16aligned(ptr, 0, 1024);

N 바이트 블록을 할당 한 다음 비운 다음 다른 N 바이트 블록을 요청하면 원래 블록이 다시 리턴 될 가능성이 매우 높습니다. 따라서 첫 번째 할당이 정렬 요구 사항을 충족하지 않으면 무한 루프가 발생할 가능성이 큽니다. 물론 많은 CPU 사이클을 낭비하면서 단일 바이트 낭비를 피할 수 있습니다.
Jonathan Leffler 2019

당신은 확실히 있습니까 %연산자에 대해 정의 된 void*의미있는 방식으로?
Ajay Brahmakshatriya 5

0

솔루션을 위해 메모리를 정렬하고 단일 바이트의 메모리를 낭비하지 않는 패딩 개념을 사용했습니다.

제약 조건이 있으면 단일 바이트를 낭비 할 수 없습니다. malloc으로 할당 된 모든 포인터는 16 바이트로 정렬됩니다.

C11이 지원되므로을 호출하면 aligned_alloc (16, size)됩니다.

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

1
많은 64 비트 시스템에서 반환되는 포인터 malloc()는 실제로 16 바이트 경계에 맞춰 정렬되어 있지만 표준에 대한 보장은 없습니다. 어떤 용도로든 잘 정렬되고 여러 32 비트 시스템에서 8 바이트 경계면 충분하고 일부의 경우 4 바이트 경계면 충분합니다.
Jonathan Leffler

0
size =1024;
alignment = 16;
aligned_size = size +(alignment -(size %  alignment));
mem = malloc(aligned_size);
memset_16aligned(mem, 0, 1024);
free(mem);

이것이 가장 간단한 구현이기를 바랍니다. 의견을 알려주십시오.


-3
long add;   
mem = (void*)malloc(1024 +15);
add = (long)mem;
add = add - (add % 16);//align to 16 byte boundary
ptr = (whatever*)(add);

추가가 malloc'd되지 않은 위치를 가리킬 것이기 때문에 문제가 있다고 생각합니다.이 방법이 확실하지 않습니다.
resultsway

@Sam이어야합니다 add += 16 - (add % 16). (2 - (2 % 16)) == 0.
SS Anne
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.