Resposta original
{
void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
Resposta fixa
{
void *mem = malloc(1024+15);
void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
Explicação conforme solicitado
O primeiro passo é alocar espaço livre suficiente, apenas por precaução. Como a memória deve estar alinhada por 16 bytes (o que significa que o endereço de byte inicial precisa ser um múltiplo de 16), adicionar 16 bytes extras garante que temos espaço suficiente. Em algum lugar nos primeiros 16 bytes, há um ponteiro alinhado de 16 bytes. (Note que malloc()
deve retornar um ponteiro que está suficientemente bem alinhado para qualquer . Propósito No entanto, o significado de 'qualquer' é principalmente para coisas como tipos básicos - long
, double
, long double
, long long
., E ponteiros para objetos e ponteiros para funções Quando você está fazendo coisas mais especializadas, como brincar com sistemas gráficos, eles podem precisar de um alinhamento mais rigoroso do que o resto do sistema - daí perguntas e respostas como essa.)
O próximo passo é converter o ponteiro nulo em um ponteiro de caracteres; Não obstante, o GCC, você não deve fazer aritmética de ponteiro em ponteiros nulos (e o GCC tem opções de aviso para informar quando você o abusar). Em seguida, adicione 16 ao ponteiro de início. Suponha que malloc()
você retornou um ponteiro impossivelmente mal alinhado: 0x800001. A adição de 16 fornece 0x800011. Agora, quero arredondar para o limite de 16 bytes - então, quero redefinir os últimos 4 bits para 0. 0x0F tem os últimos 4 bits definidos como um; portanto, ~0x0F
possui todos os bits definidos como um, exceto os quatro últimos. E isso com 0x800011 fornece 0x800010. Você pode iterar sobre os outros deslocamentos e ver que a mesma aritmética funciona.
O último passo, free()
é fácil: você sempre, e só, o retorno para free()
um valor que um dos malloc()
, calloc()
ou realloc()
devolvido a você - qualquer outra coisa é um desastre. Você forneceu corretamente mem
para manter esse valor - obrigado. O livre lança.
Por fim, se você souber sobre os componentes internos do malloc
pacote do seu sistema , poderá adivinhar que ele pode retornar dados alinhados em 16 bytes (ou alinhados em 8 bytes). Se estivesse alinhado por 16 bytes, não seria necessário alterar os valores. No entanto, isso é desonesto e não portátil - outros malloc
pacotes têm alinhamentos mínimos diferentes e, portanto, assumindo uma coisa quando faz algo diferente levaria a despejos de núcleo. Dentro de limites amplos, esta solução é portátil.
Outra pessoa mencionou posix_memalign()
como outra maneira de obter a memória alinhada; que não está disponível em todos os lugares, mas pode ser implementado com base nisso. Observe que era conveniente que o alinhamento fosse uma potência de 2; outros alinhamentos são mais confusos.
Mais um comentário - esse código não verifica se a alocação foi bem-sucedida.
Alteração
O Programador do Windows apontou que você não pode fazer operações de máscara de bits em ponteiros e, de fato, o GCC (3.4.6 e 4.3.1 testado) se queixa assim. Portanto, segue uma versão alterada do código básico - convertido em um programa principal. Também tomei a liberdade de adicionar apenas 15 em vez de 16, como foi apontado. Estou usando uintptr_t
desde que o C99 existe há tempo suficiente para ser acessível na maioria das plataformas. Se não fosse pelo uso de PRIXPTR
nas printf()
instruções, seria suficiente em #include <stdint.h>
vez de usar #include <inttypes.h>
. [Esse código inclui a correção apontada pelo CR , que estava reiterando um argumento feito por Bill K há vários anos, que eu consegui ignorar até agora.]
#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);
}
E aqui está uma versão marginalmente mais generalizada, que funcionará para tamanhos com uma potência de 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);
}
Para converter test_mask()
em uma função de alocação de uso geral, o valor de retorno único do alocador teria que codificar o endereço de liberação, como várias pessoas indicaram em suas respostas.
Problemas com os entrevistadores
Uri comentou: Talvez eu esteja tendo [um] problema de compreensão de leitura esta manhã, mas se a pergunta da entrevista disser especificamente: "Como você alocaria 1024 bytes de memória" e você claramente alocará mais do que isso. Isso não seria uma falha automática do entrevistador?
Minha resposta não se encaixa em um comentário de 300 caracteres ...
Depende, suponho. Acho que a maioria das pessoas (inclusive eu) entendeu a pergunta como "como você alocaria um espaço no qual 1024 bytes de dados podem ser armazenados e onde o endereço base é um múltiplo de 16 bytes". Se o entrevistador realmente quis dizer como você pode alocar 1024 bytes (apenas) e ter 16 bytes alinhados, as opções são mais limitadas.
- Claramente, uma possibilidade é alocar 1024 bytes e atribuir a esse endereço o 'tratamento de alinhamento'; o problema dessa abordagem é que o espaço disponível real não é determinado adequadamente (o espaço utilizável está entre 1008 e 1024 bytes, mas não havia um mecanismo disponível para especificar qual tamanho), o que o torna menos útil.
- Outra possibilidade é que você deve escrever um alocador de memória completo e garantir que o bloco de 1024 bytes retornado esteja alinhado adequadamente. Se for esse o caso, você provavelmente acaba executando uma operação bastante semelhante à que a solução proposta fez, mas a oculta dentro do alocador.
No entanto, se o entrevistador esperava alguma dessas respostas, eu esperaria que eles reconhecessem que esta solução responde a uma pergunta intimamente relacionada e, em seguida, reformulassem a pergunta para apontar a conversa na direção correta. (Além disso, se o entrevistador ficou realmente desleixado, então eu não gostaria do emprego; se a resposta a um requisito insuficientemente preciso é abatida em chamas sem correção, então o entrevistador não é alguém para quem é seguro trabalhar.)
O mundo segue em frente
O título da pergunta mudou recentemente. Foi resolver o alinhamento de memória na pergunta da entrevista C que me surpreendeu . O título revisado ( Como alocar memória alinhada usando apenas a biblioteca padrão? ) Exige uma resposta ligeiramente revisada - este adendo fornece.
Função adicionada C11 (ISO / IEC 9899: 2011) aligned_alloc()
:
7.22.3.1 aligned_alloc
função
Sinopse
#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);
Descrição
A aligned_alloc
função aloca espaço para um objeto cujo alinhamento é especificado por alignment
, cujo tamanho é especificado por size
e cujo valor é indeterminado. O valor de alignment
deve ser um alinhamento válido suportado pela implementação e pelo valor desize
deve ser um múltiplo integral de alignment
.
Retornos
A aligned_alloc
função retorna um ponteiro nulo ou um ponteiro para o espaço alocado.
E o POSIX define posix_memalign()
:
#include <stdlib.h>
int posix_memalign(void **memptr, size_t alignment, size_t size);
DESCRIÇÃO
A posix_memalign()
função deve alocar size
bytes alinhados em um limite especificado por alignment
e deve retornar um ponteiro para a memória alocada em memptr
. O valor de alignment
deve ser uma potência de dois múltiplos de sizeof(void *)
.
Após a conclusão bem-sucedida, o valor apontado por memptr
deve ser um múltiplo de alignment
.
Se o tamanho do espaço solicitado for 0, o comportamento será definido pela implementação; o valor retornado memptr
deve ser um ponteiro nulo ou um ponteiro exclusivo.
A free()
função deve desalocar a memória que foi alocada anteriormente por posix_memalign()
.
VALOR DE RETORNO
Após a conclusão bem-sucedida, posix_memalign()
retornará zero; caso contrário, um número de erro será retornado para indicar o erro.
Um ou ambos podem ser usados para responder à pergunta agora, mas apenas a função POSIX era uma opção quando a pergunta foi originalmente respondida.
Nos bastidores, a nova função de memória alinhada faz o mesmo trabalho descrito na pergunta, exceto que eles têm a capacidade de forçar o alinhamento com mais facilidade e acompanhar o início da memória alinhada internamente, para que o código não funcione. tem que lidar com especialmente - apenas libera a memória retornada pela função de alocação que foi usada.