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:
La misma expresión, cuando se asigna por primera vez a un
short
oshort *
intermedio, provoca el comportamiento esperado. Solo cuando se pasa la expresión directamente como un argumento de función, se manifiesta el comportamiento inesperado.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