Este código escribe un valor a través de un puntero si una matriz está más allá del final de otra matriz.
#include <stdio.h> #include <inttypes.h> extern int first[], second[]; #define ADDR_AFTER(ptr) ((uintptr_t)((ptr) + 1)) int test(int *an_int) { *second = 1; if (ADDR_AFTER(first) == (uintptr_t)an_int) { // ubsan does not like this. *an_int = 2; } return *second; } int first[1] = {0}, second[1] = {0}; int main() { if (ADDR_AFTER(first) == (uintptr_t)second) { printf("test: %d\n", test(second)); printf("x: %dy: %d\n", *first, *second); } }
En ningún momento comparo directamente dos punteros con diferentes objetos (porque primero los convierto a uintptr_t
). Creo un puntero a uno más allá del final de una matriz (lo cual es legal), pero nunca elimino la referencia de ese puntero. Por lo que puedo decir, esto no debería imprimir nada o imprimir:
prueba: 2 x: 0 y: 2
Que se imprime en Clang cuando la optimización es -O1
o inferior. En -O2
, sin embargo, imprime:
prueba: 1 x: 0 y: 2
Con -O2 -fsanitize=undefined
imprime esto en la salida estándar:
prueba: 2 x: 0 y: 2
y lo siguiente a stderr:
error de tiempo de ejecución: almacene en la dirección 0x000000cd9efc con espacio insuficiente para un objeto de tipo 'int' 0x000000cd9efc: nota: el puntero apunta aquí 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
con una referencia a la asignación a an_int
en test
. ¿Es este un comportamiento indefinido o es un error en Clang?
No hay nada inválido en su código, el compilador está mal. Si elimina la verificación ADDR_AFTER innecesaria en test()
, el código se ejecuta como se esperaba sin ningún error UBSan. Si lo ejecuta con la optimización habilitada y sin UBSan, obtendrá un resultado incorrecto (prueba = 1, debería ser 2).
Algo sobre el ADDR_AFTER(first) == (uintptr_t)an_int
dentro test()
hace que Clang haga algo incorrecto al compilar con -O2
.
Probé con Apple clang version 11.0.3 (clang-1103.0.32.62)
pero parece que Clang 13 y el tronco actual también tienen el error: https://godbolt.org/z/s83ncTsbf - si cambia el compilador a cualquier versión de GCC verás que puede devolver 1 o 2 desde main()
, mientras que Clang siempre devuelve 1 ( mov eax, 1
).
Probablemente deberías presentar un error de Clang para esto.