punteros matriz funciones estructura dinamica declaracion datos con codigo apuntadores c++ gcc language-lawyer endianness compiler-flags

funciones - Comportamiento indefinido de las matemáticas de puntero en una matriz de C++



punteros c++ pdf (3)

El programa tiene un comportamiento indefinido debido a lanzar un puntero alineado incorrectamente a (short*) . Esto rompe las reglas en 6.3.2.3 p6 en C11, que no tiene nada que ver con un alias estricto como se afirma en otras respuestas:

Un puntero a un tipo de objeto se puede convertir en un puntero a un tipo de objeto diferente. Si el puntero resultante no está alineado correctamente para el tipo al que se hace referencia, el comportamiento no está definido.

En [expr.static.cast] p13, C ++ dice que convertir el char* no alineado a char* short* da un valor de puntero no especificado, que podría ser un puntero no válido, que no se puede eliminar.

La forma correcta de inspeccionar los bytes es a través de char* no volviendo a short* y pretendiendo que hay un short en una dirección donde no se puede vivir un short .

¿Por qué la salida de este programa es 4 ?

#include <iostream> int main() { short A[] = {1, 2, 3, 4, 5, 6}; std::cout << *(short*)((char*)A + 7) << std::endl; return 0; }

Según tengo entendido, en el sistema Little Endian x86, donde char tiene 1 byte y 2 bytes cortos, la salida debería ser 0x0500 , porque los datos en la matriz A son tan bajos en hexadecimal:

01 00 02 00 03 00 04 00 05 00 06 00

Nos movemos desde el principio 7 bytes hacia adelante y luego leemos 2 bytes. ¿Qué me estoy perdiendo?


Estás violando estrictas reglas de alias aquí. No puedes simplemente leer a mitad de camino en un objeto y pretender que es un objeto por sí solo. No se pueden inventar objetos hipotéticos usando compensaciones de bytes como esta. GCC está perfectamente dentro de sus derechos para hacer locuras, como volver atrás en el tiempo y asesinar a Elvis Presley, cuando le entregas tu programa.

Lo que se le permite hacer es inspeccionar y manipular los bytes que conforman un objeto arbitrario, utilizando un char* . Usando ese privilegio:

#include <iostream> #include <algorithm> int main() { short A[] = {1, 2, 3, 4, 5, 6}; short B; std::copy( (char*)A + 7, (char*)A + 7 + sizeof(short), (char*)&B ); std::cout << std::showbase << std::hex << B << std::endl; } // Output: 0x500

( demostración en vivo )

Pero no puede simplemente "componer" un objeto no existente en la colección original.

Además, incluso si tiene un compilador al que se le puede decir que ignore este problema (por ejemplo, con el interruptor de -fno-strict-aliasing GCC), el objeto creado no está alineado correctamente para ninguna de las arquitecturas principales actuales. Un short no puede vivir legalmente en esa ubicación de número impar en la memoria , por lo que no puede fingir que hay uno allí. Simplemente no hay forma de evitar cuán indefinido es el comportamiento del código original; de hecho, si pasas GCC el -fsanitize=undefined switch te lo dirá.

Estoy simplificando un poco.


Esto es posiblemente un error en GCC.

Primero, debe notarse que su código está invocando un comportamiento indefinido, debido a la violación de las reglas de alias estricto.

Dicho esto, he aquí por qué lo considero un error:

  1. La misma expresión, cuando se asigna por primera vez a un short o short * intermedio, provoca el comportamiento esperado. Solo cuando se pasa la expresión directamente como un argumento de función, se manifiesta el comportamiento inesperado.

  2. Ocurre incluso cuando se compila con -O0 -fno-strict-aliasing .

Reescribí su código en C para eliminar la posibilidad de cualquier locura de C ++. Su pregunta fue etiquetada c después de todo! pshort función pshort para asegurar que la naturaleza variada printf no estuviera involucrada.

#include <stdio.h> static void pshort(short val) { printf("0x%hx ", val); } int main(void) { short A[] = {1, 2, 3, 4, 5, 6}; #define EXP ((short*)((char*)A + 7)) short *p = EXP; short q = *EXP; pshort(*p); pshort(q); pshort(*EXP); printf("/n"); return 0; }

Después de compilar con gcc (GCC) 7.3.1 20180130 (Red Hat 7.3.1-2) :

gcc -O0 -fno-strict-aliasing -g -Wall -Werror endian.c

Salida:

0x500 0x500 0x4

Parece que GCC en realidad está generando un código diferente cuando la expresión se usa directamente como un argumento, aunque estoy usando claramente la misma expresión ( EXP ).

Dumping con objdump -Mintel -S --no-show-raw-insn endian :

int main(void) { 40054d: push rbp 40054e: mov rbp,rsp 400551: sub rsp,0x20 short A[] = {1, 2, 3, 4, 5, 6}; 400555: mov WORD PTR [rbp-0x16],0x1 40055b: mov WORD PTR [rbp-0x14],0x2 400561: mov WORD PTR [rbp-0x12],0x3 400567: mov WORD PTR [rbp-0x10],0x4 40056d: mov WORD PTR [rbp-0xe],0x5 400573: mov WORD PTR [rbp-0xc],0x6 #define EXP ((short*)((char*)A + 7)) short *p = EXP; 400579: lea rax,[rbp-0x16] ; [rbp-0x16] is A 40057d: add rax,0x7 400581: mov QWORD PTR [rbp-0x8],rax ; [rbp-0x08] is p short q = *EXP; 400585: movzx eax,WORD PTR [rbp-0xf] ; [rbp-0xf] is A plus 7 bytes 400589: mov WORD PTR [rbp-0xa],ax ; [rbp-0xa] is q pshort(*p); 40058d: mov rax,QWORD PTR [rbp-0x8] ; [rbp-0x08] is p 400591: movzx eax,WORD PTR [rax] ; *p 400594: cwde 400595: mov edi,eax 400597: call 400527 <pshort> pshort(q); 40059c: movsx eax,WORD PTR [rbp-0xa] ; [rbp-0xa] is q 4005a0: mov edi,eax 4005a2: call 400527 <pshort> pshort(*EXP); 4005a7: movzx eax,WORD PTR [rbp-0x10] ; [rbp-0x10] is A plus 6 bytes ******** 4005ab: cwde 4005ac: mov edi,eax 4005ae: call 400527 <pshort> printf("/n"); 4005b3: mov edi,0xa 4005b8: call 400430 <putchar@plt> return 0; 4005bd: mov eax,0x0 } 4005c2: leave 4005c3: ret

  • Obtengo el mismo resultado con GCC 4.9.4 y GCC 5.5.0 de Docker hub