Respuesta original
{
void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
Respuesta fija
{
void *mem = malloc(1024+15);
void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
Explicación según lo solicitado
El primer paso es asignar suficiente espacio libre, por si acaso. Dado que la memoria debe estar alineada a 16 bytes (lo que significa que la dirección de byte inicial debe ser un múltiplo de 16), agregar 16 bytes adicionales garantiza que tenemos suficiente espacio. En algún lugar de los primeros 16 bytes, hay un puntero alineado de 16 bytes. (Tenga en cuenta que malloc()
se supone que devuelve un puntero que está suficientemente bien alineada para cualquier . Propósito Sin embargo, el significado de 'cualquier' es principalmente para cosas como tipos básicos - long
, double
, long double
, long long
., Y los punteros a objetos y punteros a funciones Cuando esté Al hacer cosas más especializadas, como jugar con sistemas gráficos, pueden necesitar una alineación más estricta que el resto del sistema, por lo tanto, preguntas y respuestas como esta).
El siguiente paso es convertir el puntero vacío en un puntero char; A pesar de GCC, se supone que no debe hacer aritmética de puntero en punteros nulos (y GCC tiene opciones de advertencia para informarle cuando abusa de él). Luego agregue 16 al puntero de inicio. Supongamos que le malloc()
devuelve un puntero imposiblemente mal alineado: 0x800001. Agregar los 16 da 0x800011. Ahora quiero redondear al límite de 16 bytes, por lo que quiero restablecer los últimos 4 bits a 0. 0x0F tiene los últimos 4 bits establecidos en uno; por lo tanto, ~0x0F
tiene todos los bits establecidos en uno, excepto los últimos cuatro. Y eso con 0x800011 da 0x800010. Puede iterar sobre los otros desplazamientos y ver que funciona la misma aritmética.
El último paso, free()
es fácil: siempre, y sólo, el retorno a free()
un valor que uno de malloc()
, calloc()
o realloc()
devuelto a usted - todo lo demás es un desastre. Usted proporcionó correctamente mem
para mantener ese valor, gracias. Lo libera gratis.
Finalmente, si conoce las partes internas del malloc
paquete de su sistema , podría adivinar que bien podría devolver datos alineados de 16 bytes (o podría estar alineado de 8 bytes). Si estaba alineado a 16 bytes, entonces no necesitaría analizar los valores. Sin embargo, esto es dudoso y no portátil: otros malloc
paquetes tienen diferentes alineaciones mínimas y, por lo tanto, asumir una cosa cuando hace algo diferente conduciría a volcados del núcleo. Dentro de amplios límites, esta solución es portátil.
Alguien más mencionó posix_memalign()
como otra forma de obtener la memoria alineada; eso no está disponible en todas partes, pero a menudo podría implementarse utilizando esto como base. Tenga en cuenta que era conveniente que la alineación tuviera una potencia de 2; otras alineaciones son más desordenadas.
Un comentario más: este código no verifica que la asignación se haya realizado correctamente.
Enmienda
El Programador de Windows señaló que no se pueden realizar operaciones de máscara de bits en punteros y, de hecho, GCC (3.4.6 y 4.3.1 probado) se queja así. Entonces, sigue una versión enmendada del código básico, convertida en un programa principal. También me he tomado la libertad de agregar solo 15 en lugar de 16, como se ha señalado. Estoy usando uintptr_t
ya que C99 ha existido el tiempo suficiente para ser accesible en la mayoría de las plataformas. Si no fuera por el uso de PRIXPTR
en las printf()
declaraciones, sería suficiente en #include <stdint.h>
lugar de usar #include <inttypes.h>
. [Este código incluye la solución señalada por CR , que reiteraba un punto planteado por primera vez por Bill K hace varios años, que logré pasar por alto hasta ahora].
#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);
}
Y aquí hay una versión marginalmente más generalizada, que funcionará para tamaños que tienen una potencia 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 convertir test_mask()
en una función de asignación de propósito general, el valor de retorno único del asignador tendría que codificar la dirección de publicación, como varias personas han indicado en sus respuestas.
Problemas con los entrevistadores.
Uri comentó: Tal vez tengo [un] problema de comprensión de lectura esta mañana, pero si la pregunta de la entrevista dice específicamente: "¿Cómo asignarías 1024 bytes de memoria" y claramente asignas más que eso? ¿No sería un fracaso automático del entrevistador?
Mi respuesta no cabe en un comentario de 300 caracteres ...
Depende, supongo. Creo que la mayoría de la gente (incluyéndome a mí) consideró que la pregunta significaba "¿Cómo asignaría un espacio en el que se pueden almacenar 1024 bytes de datos y donde la dirección base es un múltiplo de 16 bytes". Si el entrevistador realmente quiso decir cómo puede asignar 1024 bytes (solo) y alinearlo con 16 bytes, entonces las opciones son más limitadas.
- Claramente, una posibilidad es asignar 1024 bytes y luego dar a esa dirección el 'tratamiento de alineación'; El problema con ese enfoque es que el espacio disponible real no está correctamente determinado (el espacio utilizable está entre 1008 y 1024 bytes, pero no había un mecanismo disponible para especificar qué tamaño), lo que lo hace menos útil.
- Otra posibilidad es que se espera que escriba un asignador de memoria completa y se asegure de que el bloque de 1024 bytes que devuelve esté alineado adecuadamente. Si ese es el caso, probablemente termines haciendo una operación bastante similar a la que hizo la solución propuesta, pero la ocultas dentro del asignador.
Sin embargo, si el entrevistador esperaba cualquiera de esas respuestas, esperaría que reconocieran que esta solución responde a una pregunta estrechamente relacionada, y luego que reformule su pregunta para dirigir la conversación en la dirección correcta. (Además, si el entrevistador se pusiera realmente escaso, entonces no querría el trabajo; si la respuesta a un requisito insuficientemente preciso es derribada sin corrección, entonces el entrevistador no es alguien para quien sea seguro trabajar).
El mundo sigue adelante
El título de la pregunta ha cambiado recientemente. Fue resolver la alineación de la memoria en la pregunta de la entrevista C lo que me dejó perplejo . El título revisado ( ¿Cómo asignar memoria alineada solo usando la biblioteca estándar? ) Exige una respuesta ligeramente revisada: este apéndice lo proporciona.
Función agregada C11 (ISO / IEC 9899: 2011) aligned_alloc()
:
7.22.3.1 La aligned_alloc
función
Sinopsis
#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);
Descripción
La aligned_alloc
función asigna espacio para un objeto cuya alineación se especifica por alignment
, cuyo tamaño se especifica por size
y cuyo valor es indeterminado. El valor de alignment
será una alineación válida respaldada por la implementación y el valor de size
será un múltiplo integral de alignment
.
Devuelve
La aligned_alloc
función devuelve un puntero nulo o un puntero al espacio asignado.
Y POSIX define posix_memalign()
:
#include <stdlib.h>
int posix_memalign(void **memptr, size_t alignment, size_t size);
DESCRIPCIÓN
La posix_memalign()
función asignará size
bytes alineados en un límite especificado por alignment
, y devolverá un puntero a la memoria asignada en memptr
. El valor de alignment
será una potencia de dos múltiplos de sizeof(void *)
.
Al completar con éxito, el valor señalado por memptr
será un múltiplo de alignment
.
Si el tamaño del espacio solicitado es 0, el comportamiento está definido por la implementación; el valor devuelto memptr
será un puntero nulo o un puntero único.
La free()
función debe desasignar la memoria que previamente ha sido asignada por posix_memalign()
.
VALOR DEVUELTO
Al completar con éxito, posix_memalign()
devolverá cero; de lo contrario, se devolverá un número de error para indicar el error.
Cualquiera de estos o ambos podrían usarse para responder la pregunta ahora, pero solo la función POSIX era una opción cuando la pregunta se respondió originalmente.
Detrás de escena, la nueva función de memoria alineada hace el mismo trabajo que se describe en la pregunta, excepto que tienen la capacidad de forzar la alineación más fácilmente y realizar un seguimiento interno del inicio de la memoria alineada para que el código no tiene que tratar especialmente, solo libera la memoria devuelta por la función de asignación que se utilizó.