Cómo alinear un puntero en C

¿Hay alguna manera de alinear un puntero en C? Supongamos que estoy escribiendo datos en una stack de matriz (por lo que el puntero va hacia abajo) y quiero que los siguientes datos que escribo estén alineados en 4 para que los datos estén escritos en una ubicación de memoria que es un múltiplo de 4, ¿cómo lo haría? ¿ese?

yo tengo

uint8_t ary[1024]; ary = ary+1024; ary -= /* ... */ 

Ahora supongamos que ary apunta a la ubicación 0x05 . Quiero que apunte a 0x04 . Ahora podría hacer

 ary -= (ary % 4); 

pero C no permite modulo en punteros. ¿Hay alguna solución que sea independiente de la architecture?

Las matrices NO son punteros, a pesar de cualquier cosa que haya leído aquí en respuestas erróneas (lo que significa esta pregunta en particular o Desbordamiento de stack en general, o en cualquier otro lugar).

No puede alterar el valor representado por el nombre de una matriz como se muestra.

Lo que es confuso, quizás, es que si ary es un parámetro de función, parecerá que puede ajustar la matriz:

 void function(uint8_t ary[1024]) { ary += 213; // No problem because ary is a uint8_t pointer, not an array ... } 

Las matrices como parámetros de las funciones son diferentes de las matrices definidas ya sea fuera de una función o dentro de una función.

Tu puedes hacer:

 uint8_t ary[1024]; uint8_t *stack = ary + 510; uintptr_t addr = (uintptr_t)stack; if (addr % 8 != 0) addr += 8 - addr % 8; stack = (uint8_t *)addr; 

Esto asegura que el valor en la stack esté alineado en un límite de 8 bytes, redondeado hacia arriba. Su pregunta requiere un redondeo a un límite de 4 bytes, por lo que el código cambia a:

 if (addr % 4 != 0) addr -= addr % 4; stack = (uint8_t *)addr; 

Sí, puedes hacer eso con máscaras de bits también. Ya sea:

 addr = (addr + (8 - 1)) & -8; // Round up to 8-byte boundary 

o:

 addr &= -4; // Round down to a 4-byte boundary 

Esto solo funciona correctamente si el LHS es una potencia de dos, no para valores arbitrarios. El código con operaciones de módulo funcionará correctamente para cualquier módulo (positivo).

Consulte también: Cómo asignar memoria alineada utilizando solo la biblioteca estándar .


Código de demostración

Gnzlbg comentó :

El código para una potencia de dos interrupciones si trato de alinear, por ejemplo, uintptr_t (2) hasta un límite de 1 byte (ambas son potencias de 2: 2 ^ 1 y 2 ^ 0). El resultado es 1 pero debe ser 2 ya que 2 ya está alineado con un límite de 1 byte.

Este código demuestra que el código de alineación es correcto, siempre y cuando interpretes correctamente los comentarios que aparecen arriba (ahora aclarado con las palabras “o bien” que separan las operaciones de enmascaramiento de bits; me atraparon cuando revisé el código por primera vez).

Las funciones de alineación podrían escribirse de forma más compacta, especialmente sin las aserciones, pero el comstackdor optimizará para producir el mismo código a partir de lo que está escrito y lo que podría escribirse. Algunas de las afirmaciones podrían ser más estrictas también. Y tal vez la función de prueba debería imprimir la dirección base de la stack antes de hacer cualquier otra cosa.

El código podría, y tal vez debería, verificar que no haya desbordamiento numérico o subdesbordamiento con la aritmética. Es más probable que esto sea un problema si alinea las direcciones en un límite de varios megabytes; mientras mantiene menos de 1 KiB, alineaciones, es poco probable que encuentre un problema si no está tratando de salir de los límites de las matrices a las que tiene acceso. (Estrictamente, incluso si hace alineamientos multi-megabyte, no tendrá problemas si el resultado estará dentro del rango de memoria asignado a la matriz que está manipulando).

 #include  #include  #include  /* ** Because the test code works with pointers to functions, the inline ** function qualifier is moot. In 'real' code using the functions, the ** inline might be useful. */ /* Align upwards - arithmetic mode (hence _a) */ static inline uint8_t *align_upwards_a(uint8_t *stack, uintptr_t align) { assert(align > 0 && (align & (align - 1)) == 0); /* Power of 2 */ assert(stack != 0); uintptr_t addr = (uintptr_t)stack; if (addr % align != 0) addr += align - addr % align; assert(addr >= (uintptr_t)stack); return (uint8_t *)addr; } /* Align upwards - bit mask mode (hence _b) */ static inline uint8_t *align_upwards_b(uint8_t *stack, uintptr_t align) { assert(align > 0 && (align & (align - 1)) == 0); /* Power of 2 */ assert(stack != 0); uintptr_t addr = (uintptr_t)stack; addr = (addr + (align - 1)) & -align; // Round up to align-byte boundary assert(addr >= (uintptr_t)stack); return (uint8_t *)addr; } /* Align downwards - arithmetic mode (hence _a) */ static inline uint8_t *align_downwards_a(uint8_t *stack, uintptr_t align) { assert(align > 0 && (align & (align - 1)) == 0); /* Power of 2 */ assert(stack != 0); uintptr_t addr = (uintptr_t)stack; addr -= addr % align; assert(addr <= (uintptr_t)stack); return (uint8_t *)addr; } /* Align downwards - bit mask mode (hence _b) */ static inline uint8_t *align_downwards_b(uint8_t *stack, uintptr_t align) { assert(align > 0 && (align & (align - 1)) == 0); /* Power of 2 */ assert(stack != 0); uintptr_t addr = (uintptr_t)stack; addr &= -align; // Round down to align-byte boundary assert(addr <= (uintptr_t)stack); return (uint8_t *)addr; } static inline int inc_mod(int x, int n) { assert(x >= 0 && x < n); if (++x >= n) x = 0; return x; } typedef uint8_t *(*Aligner)(uint8_t *addr, uintptr_t align); static void test_aligners(const char *tag, Aligner align_a, Aligner align_b) { const int align[] = { 64, 32, 16, 8, 4, 2, 1 }; enum { NUM_ALIGN = sizeof(align) / sizeof(align[0]) }; uint8_t stack[1024]; uint8_t *sp = stack + sizeof(stack); int dec = 1; int a_idx = 0; printf("%s\n", tag); while (sp > stack) { sp -= dec++; uint8_t *sp_a = (*align_a)(sp, align[a_idx]); uint8_t *sp_b = (*align_b)(sp, align[a_idx]); printf("old %p, adj %.2d, A %p, B %p\n", (void *)sp, align[a_idx], (void *)sp_a, (void *)sp_b); assert(sp_a == sp_b); sp = sp_a; a_idx = inc_mod(a_idx, NUM_ALIGN); } putchar('\n'); } int main(void) { test_aligners("Align upwards", align_upwards_a, align_upwards_b); test_aligners("Align downwards", align_downwards_a, align_downwards_b); return 0; } 

Muestra de salida (parcialmente truncada):

 Align upwards old 0x7fff5ebcf4af, adj 64, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0 old 0x7fff5ebcf4be, adj 32, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0 old 0x7fff5ebcf4bd, adj 16, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0 old 0x7fff5ebcf4bc, adj 08, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0 old 0x7fff5ebcf4bb, adj 04, A 0x7fff5ebcf4bc, B 0x7fff5ebcf4bc old 0x7fff5ebcf4b6, adj 02, A 0x7fff5ebcf4b6, B 0x7fff5ebcf4b6 old 0x7fff5ebcf4af, adj 01, A 0x7fff5ebcf4af, B 0x7fff5ebcf4af old 0x7fff5ebcf4a7, adj 64, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0 old 0x7fff5ebcf4b7, adj 32, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0 old 0x7fff5ebcf4b6, adj 16, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0 old 0x7fff5ebcf4b5, adj 08, A 0x7fff5ebcf4b8, B 0x7fff5ebcf4b8 old 0x7fff5ebcf4ac, adj 04, A 0x7fff5ebcf4ac, B 0x7fff5ebcf4ac old 0x7fff5ebcf49f, adj 02, A 0x7fff5ebcf4a0, B 0x7fff5ebcf4a0 old 0x7fff5ebcf492, adj 01, A 0x7fff5ebcf492, B 0x7fff5ebcf492 … old 0x7fff5ebcf0fb, adj 08, A 0x7fff5ebcf100, B 0x7fff5ebcf100 old 0x7fff5ebcf0ca, adj 04, A 0x7fff5ebcf0cc, B 0x7fff5ebcf0cc old 0x7fff5ebcf095, adj 02, A 0x7fff5ebcf096, B 0x7fff5ebcf096 Align downwards old 0x7fff5ebcf4af, adj 64, A 0x7fff5ebcf480, B 0x7fff5ebcf480 old 0x7fff5ebcf47e, adj 32, A 0x7fff5ebcf460, B 0x7fff5ebcf460 old 0x7fff5ebcf45d, adj 16, A 0x7fff5ebcf450, B 0x7fff5ebcf450 old 0x7fff5ebcf44c, adj 08, A 0x7fff5ebcf448, B 0x7fff5ebcf448 old 0x7fff5ebcf443, adj 04, A 0x7fff5ebcf440, B 0x7fff5ebcf440 old 0x7fff5ebcf43a, adj 02, A 0x7fff5ebcf43a, B 0x7fff5ebcf43a old 0x7fff5ebcf433, adj 01, A 0x7fff5ebcf433, B 0x7fff5ebcf433 old 0x7fff5ebcf42b, adj 64, A 0x7fff5ebcf400, B 0x7fff5ebcf400 old 0x7fff5ebcf3f7, adj 32, A 0x7fff5ebcf3e0, B 0x7fff5ebcf3e0 old 0x7fff5ebcf3d6, adj 16, A 0x7fff5ebcf3d0, B 0x7fff5ebcf3d0 old 0x7fff5ebcf3c5, adj 08, A 0x7fff5ebcf3c0, B 0x7fff5ebcf3c0 old 0x7fff5ebcf3b4, adj 04, A 0x7fff5ebcf3b4, B 0x7fff5ebcf3b4 old 0x7fff5ebcf3a7, adj 02, A 0x7fff5ebcf3a6, B 0x7fff5ebcf3a6 old 0x7fff5ebcf398, adj 01, A 0x7fff5ebcf398, B 0x7fff5ebcf398 … old 0x7fff5ebcf0f7, adj 01, A 0x7fff5ebcf0f7, B 0x7fff5ebcf0f7 old 0x7fff5ebcf0d3, adj 64, A 0x7fff5ebcf0c0, B 0x7fff5ebcf0c0 old 0x7fff5ebcf09b, adj 32, A 0x7fff5ebcf080, B 0x7fff5ebcf080 

Estoy editando esta respuesta porque:

  1. Tuve un error en mi código original (olvidé un tipo de intptr_t a intptr_t ), y
  2. Estoy respondiendo a las críticas de Jonathan Leffler para aclarar mi intención.

El código siguiente no implica que puede cambiar el valor de una matriz ( foo ). Pero puede obtener un puntero alineado en esa matriz, y este ejemplo ilustra una forma de hacerlo.

 #define alignmentBytes ( 1 << 2 ) // == 4, but enforces the idea that that alignmentBytes should be a power of two #define alignmentBytesMinusOne ( alignmentBytes - 1 ) uint8_t foo[ 1024 + alignmentBytesMinusOne ]; uint8_t *fooAligned; fooAligned = (uint8_t *)((intptr_t)( foo + alignmentBytesMinusOne ) & ~alignmentBytesMinusOne); 

¡NO USE MODULO! ¡ES REALMENTE LENTO! Sin duda, la manera más rápida de alinear un puntero es usar el complemento 2 de matemáticas. Necesita invertir los bits, agregar uno y enmascarar los 2 bits menos significativos (para 32 bits) o 3 (para 64 bits). El resultado es un desplazamiento que luego se agrega al valor del puntero para alinearlo. Funciona muy bien para números de 32 y 64 bits. Para la alineación de 16 bits, simplemente enmascare el puntero con 0x1 y agregue ese valor. Algorithm funciona de manera idéntica en cualquier idioma, pero como puede ver, Embedded C ++ es muy superior a C en todos los sentidos de forma y forma.

 #include  /** Returns the number to add to align the given pointer to a 8, 16, 32, or 64-bit boundary. @author Cale McCollough. @param ptr The address to align. @return The offset to add to the ptr to align it. */ template inline uintptr_t MemoryAlignOffset (const void* ptr) { return ((~reinterpret_cast (ptr)) + 1) & (sizeof (T) - 1); } /** Word aligns the given byte pointer up in addresses. @author Cale McCollough. @param ptr Pointer to align. @return Next word aligned up pointer. */ template inline T* MemoryAlign (T* ptr) { uintptr_t offset = MemoryAlignOffset (ptr); char* aligned_ptr = reinterpret_cast (ptr) + offset; return reinterpret_cast (aligned_ptr); } 

Para redacciones y pruebas detalladas, por favor, consulte https://github.com/kabuki-starship/kabuki-toolkit/wiki/Fastest-Method-to-Align-Pointers . Si desea ver una prueba de por qué nunca debe usar modulo, inventé el algoritmo del número entero más rápido del mundo. El punto de referencia en el documento muestra el efecto de optimizar solo una instrucción de módulo. Por favor, consulte https://github.com/kabuki-starship/kabuki-toolkit/wiki/Engineering-a-Faster-Integer-to-String-Algorithm .

Gráfico de por qué no deberías usar modulo