Jak przydzielić pamięć wyrównaną tylko przy użyciu biblioteki standardowej?

Właśnie skończyłem test w ramach rozmowy kwalifikacyjnej i jedno pytanie mnie zaskoczyło - nawet za pomocą google w celach informacyjnych. Chciałbym zobaczyć, co może z tym zrobić ekipa stackoverflow:

Funkcja "memset_16aligned" wymaga 16-bajtowego wyrównanego wskaźnika przekazanego do niej, w przeciwnym razie ulegnie awarii.

A) jak przydzielić 1024 bajty pamięci i wyrównać ją do granicy 16 bajtów?
b) zwolnić pamięć po wykonaniu memset_16aligned.

{

   void *mem;

   void *ptr;

   // answer a) here

   memset_16aligned(ptr, 0, 1024);

   // answer b) here

}
Author: legends2k, 2008-10-23

17 answers

Oryginalna odpowiedź

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

Stała odpowiedź

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

Wyjaśnienie zgodnie z żądaniem

Pierwszym krokiem jest przydzielenie wystarczającej ilości wolnego miejsca, na wszelki wypadek. Ponieważ pamięć musi być wyrównana 16-bajtowo (co oznacza, że adres wiodący musi być wielokrotnością 16), dodanie 16 dodatkowych bajtów gwarantuje, że mamy wystarczająco dużo miejsca. Gdzieś w pierwszych 16 bajtach znajduje się 16-bajtowy wyrównany wskaźnik. (Zauważ, że malloc() ma zwracać wskaźnik, który jest wystarczająco dobrze dopasowane do dowolnego celu. Jednak znaczenie " any " dotyczy przede wszystkim takich rzeczy jak podstawowe typy - long, double, long double, long long, i wskaźniki do obiektów i wskaźniki do funkcji. Kiedy robisz bardziej specjalistyczne rzeczy, takie jak granie z systemami graficznymi, mogą one wymagać bardziej rygorystycznego wyrównania niż reszta systemu - stąd pytania i odpowiedzi takie jak to.)

Następnym krokiem jest konwersja wskaźnika void na wskaźnik char; niezależnie od GCC, nie jesteś ma robić arytmetykę wskaźnika na wskaźnikach void (a GCC ma opcje ostrzegawcze, które informują cię, gdy go nadużywasz). Następnie dodaj 16 do wskaźnika początkowego. Załóżmy, że malloc() zwrócił Ci niewiarygodnie źle wyrównany wskaźnik: 0x800001. Dodanie 16 daje 0x800011. Teraz chcę zaokrąglić do granicy 16-bajtów - więc chcę zresetować ostatnie 4 bity do 0. 0x0F ma 4 ostatnie bity ustawione na jeden; dlatego ~0x0F ma wszystkie bity ustawione na jeden z wyjątkiem czterech ostatnich. I to z 0x800011 daje 0x800010. Możesz iterować nad innymi offsetami i zobaczyć, że ta sama arytmetyka działa.

Ostatni krok, free(), jest łatwy: zawsze i tylko Ty zwracasz free() wartość, którą jeden z malloc(), calloc() albo realloc() wrócił do Ciebie-Wszystko inne jest katastrofą. Prawidłowo podałeś mem, aby utrzymać tę wartość-dziękuję. The free GO uwalnia.

Wreszcie, jeśli wiesz o wewnętrznych pakietach systemu malloc, możesz zgadnąć, że może on zwracać 16-bajtowe wyrównane dane (lub może to być 8-bajtowe wyrównane). Gdyby był wyrównany 16-bajtowo, nie musiałbyś łączyć się z wartościami. Jest to jednak podejrzane i nie-Przenośne-inne pakiety malloc mają różne minimalne wyrównania, a zatem założenie jednej rzeczy, gdy zrobi coś innego, prowadziłoby do zrzutów pamięci. W szerokim zakresie rozwiązanie to jest przenośne.

Ktoś inny wspomniał posix_memalign() jako inny sposób na uzyskanie wyrównanej pamięci; która nie jest dostępna wszędzie, ale często może być zaimplementowana za pomocą tego podstawa. Zauważ, że było wygodne, że wyrównanie było mocą 2; inne wyrównania są messier.

Jeszcze jeden komentarz - ten kod nie sprawdza, czy przydział się powiódł.

Poprawka

Programista Windows zauważył, że nie można wykonywać operacji masek bitowych na wskaźnikach, a GCC (testowane w wersjach 3.4.6 i 4.3.1) narzeka w ten sposób. Tak więc następuje zmieniona wersja podstawowego kodu-przekonwertowana na program główny. Wzięłam też wolność dodawania tylko 15 zamiast 16, Jak już wspomniano. Używam uintptr_t ponieważ C99 istnieje wystarczająco długo, aby być dostępne na większości platform. Gdyby nie użycie PRIXPTR w poleceniach printf(), wystarczyłoby #include <stdint.h> zamiast #include <inttypes.h>. [ten kod zawiera poprawkę wskazaną przez C. R. , która powtarzała punkt po raz pierwszy wykonany przez Bill K kilka lat temu, który udało mi się przeoczyć, aż teraz.]

#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);
}
A oto nieco bardziej uogólniona wersja, która będzie działać dla rozmiarów, które są potęgą 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);
}

Aby przekształcić test_mask() w funkcję alokacji ogólnego przeznaczenia, pojedyncza wartość zwracana z alokatora musiałaby zakodować adres Wydania, jak wskazało kilka osób w swoich odpowiedziach.

Problemy z rozmówcami

Uri skomentował: może dziś rano mam problem z czytaniem ze zrozumieniem, ale jeśli pytanie wywiadu mówi konkretnie: "jak przydzielisz 1024 bajtów pamięci" i wyraźnie przydzielisz więcej niż to. Czy to nie byłaby automatyczna porażka rozmówcy?

Moja odpowiedź nie zmieści się w 300-znakowym komentarzu...

To chyba zależy. Myślę, że większość ludzi (w tym ja) wzięła to pytanie na myśli "jak przydzielisz miejsce, w którym można przechowywać 1024 bajty danych, a gdzie adres bazowy jest wielokrotnością 16 bajtów". Jeśli interviewer naprawdę chodziło o to, jak można przydzielić 1024 bajty (tylko) i wyrównać je 16-bajtowo, wtedy opcje są bardziej ograniczone.
  • oczywiście, jedną z możliwości jest przydzielenie 1024 bajtów, a następnie nadanie temu adresowi "wyrównania"; problem z tym podejściem polega na tym, że rzeczywista dostępna przestrzeń nie jest prawidłowo określona (przestrzeń użytkowa mieści się między 1008 a 1024 bajtami, ale nie było dostępnego mechanizmu, który określałby rozmiar), co sprawia, że jest ona mniejsza niż 1008 bajtów. przydatne.
  • inną możliwością jest napisanie pełnego alokatora pamięci i upewnienie się, że zwracany blok 1024-bajtowy jest odpowiednio wyrównany. W takim przypadku prawdopodobnie wykonujesz operację dość podobną do tej, którą wykonało proponowane rozwiązanie, ale ukrywasz ją wewnątrz alokatora.

Jednakże, jeśli ankieter oczekiwał którejkolwiek z tych odpowiedzi, oczekuję, że uznają, że to rozwiązanie odpowiada na ściśle powiązane pytanie, a następnie refraktować ich pytanie, aby skierować rozmowę we właściwym kierunku. (Ponadto, jeśli ankieter stał się naprawdę uparty, to nie chciałbym tej pracy; jeśli odpowiedź na niewystarczająco precyzyjny wymóg zostanie zestrzelona w płomieniach bez korekty, to ankieter nie jest kimś, dla kogo jest bezpiecznie pracować.)

The world moves on

Tytuł pytania zmienił się ostatnio. To było rozwiązać wyrównanie pamięci w pytaniu wywiadu C, które zatkało me . Poprawiony tytuł (jak przydzielić pamięć wyrównaną tylko przy użyciu biblioteki standardowej?) wymaga nieco zmienionej odpowiedzi - ten dodatek ją dostarcza.

C11 (ISO/IEC 9899:2011) dodana funkcja aligned_alloc():

7.22.3.1 funkcja aligned_alloc

Synopsis

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

Opis
Funkcja aligned_alloc przydziela przestrzeń dla obiektu, którego wyrównanie jest określony przez alignment, którego rozmiar jest określony przez size, A którego wartość jest nieokreślony. Wartość alignment jest poprawnym wyrównaniem wspieranym przez implementację, a wartość size jest całkowalną wielokrotnością alignment.

Returns
Funkcja aligned_alloc zwraca wskaźnik null lub wskaźnik do przydzielonej przestrzeni.

I POSIX definiuje posix_memalign():

#include <stdlib.h>

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

Opis

Funkcja posix_memalign() powinna przydziela size bajty wyrównane do granicy określonej przez alignment i zwraca wskaźnik do przydzielonej pamięci w memptr. Wartość alignment jest potęgą dwóch wielokrotności sizeof(void *).

Po pomyślnym zakończeniu wartość wskazywana przez memptr jest wielokrotnością alignment.

Jeśli żądany rozmiar przestrzeni wynosi 0, zachowanie jest zdefiniowane w implementacji; wartość zwracana w memptr musi być albo wskaźnikiem null, albo unikalnym wskaźnikiem.

The free() funkcja dealokuje pamięć wcześniej przydzieloną przez posix_memalign().

WARTOŚĆ ZWRACANA

Po pomyślnym zakończeniu, posix_memalign() zwraca zero; w przeciwnym razie zwracany jest numer błędu wskazujący błąd.

Jedna lub obie z nich mogą być użyte do odpowiedzi na pytanie teraz, ale tylko funkcja POSIX była opcją, gdy pytanie zostało pierwotnie udzielone.

[56]}za kulisami, nowa funkcja wyrównanej pamięci robi wiele to samo zadanie, co opisane w pytaniu, z tym, że mają możliwość łatwiejszego wymuszenia wyrównania i śledzenia początku wyrównanej pamięci wewnętrznie, aby kod nie musiał się specjalnie zajmować - po prostu zwalnia pamięć zwróconą przez funkcję alokacji, która została użyta.
 545
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:37

Trzy nieco różne odpowiedzi w zależności od tego, jak patrzysz na pytanie:

1) wystarczajÄ ... co na dokĹ ' adne pytanie zadane jest rozwiÄ ... zanie Jonathana Lefflera, z tym, Ĺźe aby zaokrÄ ... gnÄ ... Ä ‡ do 16-wyrównanych, potrzebujesz tylko 15 dodatkowych bajtăłw, a nie 16.

A:

/* 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;

B:

free(mem);

2) dla bardziej ogólnej funkcji alokacji pamięci, wywołujący nie chce mieć dwóch wskaźników (jeden do użycia i jeden do uwolnienia). Więc przechowujesz wskaźnik do "prawdziwego" bufora poniżej wyrównany bufor.

A:

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;

B:

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

Zauważ, że w przeciwieństwie do (1), gdzie tylko 15 bajtów zostało dodanych do mem, ten kod może faktycznie zmniejszyć wyrównanie, jeśli twoja implementacja zagwarantuje wyrównanie 32-bajtowe z malloc (mało prawdopodobne, ale teoretycznie implementacja C może mieć wyrównany Typ 32-bajtowy). To nie ma znaczenia, jeśli tylko wywołasz memset_16aligned, ale jeśli użyjesz pamięci do struktury, to może to mieć znaczenie.

Nie jestem oczywiście poza tym, jaka jest dobra poprawka (poza ostrzeżeniem użytkownika, że zwrócony bufor nie musi być odpowiedni dla dowolnych struktur), ponieważ nie ma możliwości programowego określenia, jaka jest gwarancja wyrównania specyficzna dla implementacji. Domyślam się, że przy starcie można przydzielić dwa lub więcej 1-bajtowych buforów i założyć, że najgorsze wyrównanie, które widzisz, to wyrównanie gwarantowane. Jeśli się mylisz, marnujesz pamięć. Kto ma lepszy pomysł, niech powie więc...

[Dodano : "Standardowa" sztuczka polega na stworzeniu Unii "typów, które mogą być maksymalnie wyrównane" w celu określenia wymaganego wyrównania. Maksymalnie wyrównane typy mogą być (w C99) 'long long', 'long double', 'void *', lub ' void (*)(void)'; Jeśli włączysz <stdint.h>, Możesz prawdopodobnie użyć 'intmax_t ' zamiast long long (i, na maszynach Power 6 (AIX), intmax_t da ci 128-bitowy Typ integer). Wymagania dostosowawcze dla tej unii można określić poprzez włączenie jej do struktura z pojedynczym znakiem, po którym następuje Unia:

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;

Następnie należy użyć większego z żądanego wyrównania (w przykładzie 16) i align obliczonej powyżej wartości.

NA (64-bitowym) Solarisie 10 wygląda na to, że podstawowe wyrównanie dla wyniku malloc() jest wielokrotnością 32 bajtów.
]

W praktyce alokatory wyrównane często przyjmują parametr wyrównania, a nie są podłączone do sieci. Więc użytkownik przejdzie w wielkości struktury, którą mu zależy o (lub najmniejszej mocy 2 większej lub równej temu) i wszystko będzie dobrze.

3) Użyj tego, co zapewnia twoja platforma: posix_memalign dla POSIX, _aligned_malloc Dla Windows.

4) Jeśli używasz C11, To najczystszą - przenośną i zwięzłą - opcją jest użycie funkcji biblioteki standardowej aligned_alloc to zostało wprowadzone w tej wersji specyfikacji języka.

 54
Author: Steve Jessop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-10 11:43:37

Możesz też spróbować posix_memalign() (Oczywiście na platformach POSIX).

 37
Author: florin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-12 21:44:49

Oto alternatywne podejście do części "zaokrąglić". Nie jest to najlepiej zakodowane rozwiązanie, ale wykonuje zadanie, a tego typu składnia jest nieco łatwiejsza do zapamiętania(plus działa na wartości wyrównania, które nie są potęgą 2). Rzut uintptr_t był konieczny, aby uspokoić kompilator; arytmetyka wskaźnikowa nie jest zbyt lubiana w dzieleniu lub mnożeniu.

void *mem = malloc(1024 + 15);
void *ptr = (void*) ((uintptr_t) mem + 15) / 16 * 16;
memset_16aligned(ptr, 0, 1024);
free(mem);
 19
Author: An̲̳̳drew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-24 17:59:13

Niestety, w C99 wydaje się dość trudne, aby zagwarantować dostosowanie dowolnego rodzaju w sposób, który byłby przenośny w każdej implementacji C zgodnej z C99. Dlaczego? Ponieważ wskaźnik nie jest gwarantowany jako "adres bajtowy", można sobie wyobrazić z płaskim modelem pamięci. Podobnie jak reprezentacja uintptr_t nie jest tak gwarantowana, która sama w sobie jest typem opcjonalnym.

Możemy wiedzieć o niektórych implementacjach, które używają reprezentacji dla void * (i przez definition, także char * ), który jest prostym adresem bajtowym, ale przez C99 jest nieprzejrzysty dla nas, programistów. Implementacja może reprezentować wskaźnik przez zbiór {segment, offset} gdzie offset może mieć wyrównanie "w rzeczywistości."Dlaczego, wskaźnik może być nawet jakąś formą wartości wyszukiwania tabel hash lub nawet wartością wyszukiwania linked-list. Może zakodować informacje o granicach.

W ostatnim projekcie c1x dla standardu C widzimy _alignas Słowo kluczowe. To może trochę pomóc.

Jedyną gwarancją, jaką daje nam C99, jest to, że funkcje alokacji pamięci zwrócą wskaźnik odpowiedni do przypisania do wskaźnika wskazującego na dowolny typ obiektu. Ponieważ nie możemy określić wyrównania obiektów, nie możemy zaimplementować własnych funkcji alokacji z odpowiedzialnością za wyrównanie w dobrze zdefiniowany, przenośny sposób.

Dobrze byłoby się mylić co do tego twierdzenia.

 18
Author: Shao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-14 10:18:28

Na froncie wypełnienia o liczbie bajtów 16 vs 15, rzeczywista liczba, którą musisz dodać, aby uzyskać wyrównanie N, wynosi max(0,N-M) gdzie M jest naturalnym wyrównaniem alokatora pamięci (i oba są potęgami 2).

Ponieważ minimalne wyrównanie pamięci dowolnego alokatora wynosi 1 bajt, 15=max(0,16-1) jest konserwatywną odpowiedzią. Jeśli jednak wiesz, że alokator pamięci da Ci 32-bitowe wyrównane adresy int( co jest dość powszechne), możesz użyć 12 jako podkładka.

Nie jest to ważne w tym przykładzie, ale może być ważne w systemie wbudowanym z 12K pamięci RAM, gdzie liczy się każda zapisana liczba int.

Najlepszym sposobem na zaimplementowanie go, jeśli zamierzasz próbować zapisać każdy możliwy bajt, jest makro, dzięki któremu możesz go dostosować do swojej macierzystej pamięci. Ponownie, jest to prawdopodobnie przydatne tylko dla systemów wbudowanych, gdzie trzeba zapisać każdy bajt.

W poniższym przykładzie, w większości systemów wartość 1 jest odpowiednia dla MEMORY_ALLOCATOR_NATIVE_ALIGNMENT, jednak dla naszego teoretycznego systemu wbudowanego z 32-bitowymi alokacjami wyrównanymi, następujące mogą zaoszczędzić trochę cennej pamięci:]}

#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)
 15
Author: Adisak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-21 16:40:40

Być może byliby zadowoleni ze znajomości memalign ? I jak zauważa Jonathan Leffler, istnieją dwie nowsze funkcje, o których warto wiedzieć.

UPS, florin mnie wyprzedził. Jeśli jednak przeczytasz stronę podręcznika, z którą się połączyłem, najprawdopodobniej zrozumiesz przykład podany przez wcześniejszy plakat.
 8
Author: Don Wakefield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-13 03:04:19

Robimy takie rzeczy cały czas dla przyspieszenia.framework, mocno wektoryzowana biblioteka OS X / iOS, w której musimy cały czas zwracać uwagę na wyrównanie. Istnieje sporo opcji, jedna lub dwie z nich nie widziałem wymienione powyżej.

Najszybszą metodą dla takiej małej tablicy jest przyklejenie jej na stos. Z GCC / clang:

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

Nie jest wymagana funkcja free (). Zazwyczaj są to dwie instrukcje: odjmij 1024 ze wskaźnika stosu, then I wskaźnik stosu / align = "left"/ Prawdopodobnie żądający potrzebował danych na stercie, ponieważ jego żywotność tablicy przekroczyła stos lub rekurencja jest w pracy lub przestrzeń stosu jest na poważnej premii.

Na OS X / iOS wszystkie połączenia do malloc/calloc / etc. są zawsze wyrównane 16 bajtów. Jeśli na przykład potrzebujesz wyrównania 32 bajtów dla AVX, możesz użyć posix_memalign:

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

Niektórzy wspominali o interfejsie C++, który działa podobnie.

Nie należy zapominać, że strony są wyrównane do dużych potęg dwóch, więc bufory wyrównane do strony są wyrównane do 16 bajtów. Tak więc, mmap () i valloc () oraz inne podobne interfejsy są również opcjami. mmap () ma tę zaletę, że bufor może być prenitializowany z czymś niezerowym, jeśli chcesz. Ponieważ mają one Rozmiar wyrównany do strony, nie otrzymasz z nich minimalnej alokacji i prawdopodobnie będzie to spowodowane błędem maszyny Wirtualnej przy pierwszym dotknięciu.

[[3]}Cheesy: Włącz guard malloc lub podobne. Bufory rozmiar N*16 bajtów, taki jak ten, będzie równy N * 16 bajtów, ponieważ maszyna wirtualna jest używana do przechwytywania przekroczeń, a jej granice znajdują się na granicach stron. / Align = "Left" / funkcje ramowe przyjmują dostarczony przez użytkownika bufor temp, aby używać go jako przestrzeni zarysowań. Tutaj musimy założyć, że bufor przekazany do nas jest szalenie niedopasowany, a użytkownik aktywnie stara się utrudnić nasze życie na złość. (Nasze przypadki testowe przyklejają stronę ochronną tuż przed i za buforem temp, aby podkreślić złośliwość.) Tutaj zwracamy minimalny rozmiar potrzebny do zagwarantowania 16-bajtowego wyrównanego segmentu gdzieś w nim, a następnie ręcznie wyrównujemy bufor. Ten rozmiar to desired_size + alignment - 1. Tak więc w tym przypadku jest to 1024 + 16 - 1 = 1039 bajtów Następnie wyrównaj tak:
#include <stdint.h>
void My_func( uint8_t *tempBuf, ... )
{
    uint8_t *alignedBuf = (uint8_t*) 
                          (((uintptr_t) tempBuf + ((uintptr_t)alignment-1)) 
                                        & -((uintptr_t) alignment));
    ...
}

dodanie alignment - 1 spowoduje przesunięcie wskaźnika poza pierwszy adres wyrównany, a następnie dodanie-alignment (np. 0xfff...ff0 Dla wyrównania=16) przywraca go do wyrównanego adresu.

Jako opisane przez inne posty, w innych systemach operacyjnych Bez 16-bajtowych gwarancji wyrównania, możesz wywołać malloc z większym rozmiarem, odstawić wskaźnik za free() później, a następnie wyrównać jak opisano bezpośrednio powyżej i użyć wyrównanego wskaźnika, tak jak opisano w przypadku naszego bufora temp.

Jeśli chodzi o aligned_memset, to jest to raczej głupie. Musisz tylko zapętlić do 15 bajtów, aby dotrzeć do wyrównanego adresu, a następnie przejść do wyrównanych sklepów z możliwym kodem czyszczenia na koniec. Można nawet czyścić bity w kodzie wektorowym, albo jako nieprzypisane sklepy, które nakładają się na wyrównany region (pod warunkiem, że długość jest co najmniej długością wektora) lub za pomocą czegoś takiego jak movmaskdqu. Ktoś jest leniwy. Jest to jednak prawdopodobnie rozsądne pytanie, jeśli ankieter chce wiedzieć, czy czujesz się komfortowo z stdint.H, operatory bitowe i podstawy pamięci, więc wymyślony przykład można wybaczyć.

 5
Author: Ian Ollmann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-05 05:19:14

Jestem zaskoczony, że nikt nie głosował Shao 's odpowiedź , że, jak rozumiem, niemożliwe jest wykonanie tego, o co prosi się w standardzie C99, ponieważ konwersja wskaźnika na typ całkowy formalnie jest niezdefiniowanym zachowaniem. (Oprócz standardu umożliwiającego konwersję uintptr_t void*, ale standard nie wydaje się pozwalać na jakiekolwiek manipulacje wartością uintptr_t, a następnie jej konwersję z powrotem.)

 5
Author: Lutorm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:37

Użycie memalign, Aligned-Memory-Blocks może być dobrym rozwiązaniem problemu.

 3
Author: neuron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-12 18:09:09

Pierwszą rzeczą, która wpadła mi do głowy podczas czytania tego pytania, było zdefiniowanie wyrównanej struktury, utworzenie jej instancji, a następnie wskazanie na nią.

Czy Jest jakiś fundamentalny powód, dla którego tęsknię, skoro nikt inny tego nie zasugerował?

Jako notatka boczna, ponieważ użyłem tablicy znaków (zakładając, że znak systemu jest 8 bitów (tj. 1 bajt)), nie widzę potrzeby atrybutu ((spakowany)) koniecznie (popraw mnie, jeśli się mylę), ale i tak go wstawiłem.

To działa na dwóch systemy próbowałem go na, ale jest możliwe, że jest optymalizacja kompilatora, że jestem nieświadomy dając mi fałszywych pozytywów vis-a-vis skuteczności kodu. Używałem gcc 4.9.2 na OSX i GCC 5.2.1 na Ubuntu.

#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;
}
 3
Author: J-a-n-u-s,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-10 21:28:41

MacOS X specific:

  1. wszystkie wskaźniki przypisane do malloc są wyrównane do 16 bajtów.
  2. C11 jest obsługiwany, więc możesz po prostu wywołać aligned_malloc (16, size).

  3. MacOS X wybiera kod, który jest zoptymalizowany pod kątem poszczególnych procesorów w czasie rozruchu dla memset, memcpy i memmove, a ten kod wykorzystuje sztuczki, o których nigdy nie słyszałeś, aby uczynić go szybkim. 99% szans, że memset działa szybciej niż jakikolwiek ręcznie napisany memset16, co sprawia, że całe pytanie jest bezcelowe.

Jeśli chcesz 100% przenośnego rozwiązania, przed C11 nie ma żadnego. Ponieważ nie ma przenośnego sposobu na testowanie wyrównania wskaźnika. Jeśli nie musi być w 100% przenośny, możesz użyć

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

Zakłada się, że wyrównanie wskaźnika jest przechowywane w najniższych bitach podczas konwersji wskaźnika na niepodpisaną liczbę całkowitą. Konwersja do niepodpisanej liczby całkowitej traci informacje i jest zdefiniowana w implementacji, ale to nie ma znaczenia, ponieważ nie konwertujemy wyniku z powrotem na wskaźnik.

Najgorsze jest to, że oryginalny wskaźnik musi być gdzieś zapisany, aby wywołać z nim funkcję free (). Podsumowując, naprawdę wątpiłbym w mądrość tego projektu.

 1
Author: Chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-25 13:44:21

Wystarczy użyć memalign? http://linux.die.net/man/3/memalign

 0
Author: PhonicUK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-04 11:35:55

Możesz także dodać jakieś 16 bajtów, a następnie wcisnąć oryginalny ptr do 16 bitów, dodając (16-mod) jak poniżej wskaźnika:

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
Author: resultsway,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-25 18:27:14

Jeśli istnieją ograniczenia, że nie można zmarnować jednego bajtu, To rozwiązanie to działa: Uwaga: jest przypadek, w którym można to wykonać w nieskończoność: D

   void *mem;  
   void *ptr;
try:
   mem =  malloc(1024);  
   if (mem % 16 != 0) {  
       free(mem);  
       goto try;
   }  
   ptr = mem;  
   memset_16aligned(ptr, 0, 1024);
 0
Author: Deepthought,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-25 14:06:59

Do rozwiązania użyłem koncepcji wyściółki, która wyrównuje pamięć i nie marnuje pamięć pojedynczego bajtu .

Jeśli istnieją ograniczenia, nie można zmarnować ani jednego bajtu. Wszystkie wskaźniki przypisane do malloc są wyrównane do 16 bajtów.

C11 jest obsługiwany, więc możesz po prostu wywołać aligned_malloc (16, size).

void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
 0
Author: user3415603,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-14 14:05:20
long add;   
mem = (void*)malloc(1024 +15);
add = (long)mem;
add = add - (add % 16);//align to 16 byte boundary
ptr = (whatever*)(add);
 -2
Author: Ramana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-09 05:49:49