c++ - punteros - ¿Restringe la ayuda en C si un puntero ya está marcado const?
punteros como parametros de funciones en c (5)
Su puntero es constante, diciéndole a cualquiera que llame a su función que no tocará los datos a los que se apunta a través de esa variable. Desafortunadamente, el compilador aún no sabrá si el resultado es un alias de los punteros const. Siempre puede usar un puntero no const como const-pointer. Por ejemplo, muchas funciones toman un puntero const char (es decir, cadena) como parámetro, pero puede, si lo desea, pasarle un puntero que no sea const, la función es simplemente hacer una promesa de que no usará ese particular puntero para cambiar cualquier cosa.
Básicamente, para acercarte a tu pregunta, deberías agregar restringir a ayb para ''prometer'' al compilador que quien usa esta función no pasará el resultado como un alias a a o b. Suponiendo, por supuesto, que puedas hacer tal promesa.
Solo me pregunto: cuando agrego restringir a un puntero, le digo al compilador que el puntero no es un alias para otro puntero. Supongamos que tengo una función como:
// Constructed example
void foo (float* result, const float* a, const float* b, const size_t size)
{
for (size_t i = 0; i < size; ++i)
{
result [i] = a [0] * b [i];
}
}
Si el compilador tiene que suponer que el result
puede superponerse con a
, debe volver a buscarlo cada vez. Pero, como a
se marca const
, el compilador también podría suponer que a está fijo y, por lo tanto, recuperarlo una vez está bien.
La pregunta es, en una situación como esta, ¿cuál es la forma recomendada de trabajar con Restringir? Seguramente no quiero que el compilador vuelva a buscar cada vez, pero no pude encontrar buena información sobre cómo se supone que debe funcionar el restrict
aquí.
Como se indicó en la respuesta anterior, debe agregar "restringir". También quería comentar su escenario de que "el resultado podría superponerse con un". Esa no es la única razón por la que el compilador detectará que "a" podría cambiar. También podría ser cambiado por otro hilo que tenga un puntero a "a". Por lo tanto, incluso si su función no cambió ningún valor, el compilador asumirá que "a" podría cambiar.
En el estándar C-99 (ISO / IEC 9899: 1999 (E)) hay ejemplos de const * restrict
de const * restrict
, por ejemplo, en la sección 7.8.2.3:
Las funciones strtoimax y strtoumax
Sinopsis
#include <inttypes.h>
intmax_t strtoimax(const char * restrict nptr,
char ** restrict endptr, int base);
--- snip ---
Por lo tanto, si se supone que el estándar no proporcionaría un ejemplo si const *
fuera redundante para * restrict
, entonces, de hecho, no es redundante.
Todos aquí parecen muy confundidos. No hay un solo ejemplo de un puntero de const en ninguna respuesta hasta el momento.
La declaración const float* a
no es un puntero const, es const storage. El puntero sigue siendo mutable. float *const a
es un puntero const para un float mutable.
Entonces la pregunta debería ser, ¿hay algún punto en float *const restrict a
(o const float *const restrict a
si lo prefiere).
Sí, necesitas restringir Pointer-to-const no significa que nada puede cambiar los datos, solo que no puede cambiarlo a través de ese puntero .
const
es principalmente un mecanismo para pedirle al compilador que lo ayude a realizar un seguimiento de las cosas que desea modificar. const
no es una promesa para el compilador de que una función realmente no modificará los datos .
A diferencia de restrict
, usar puntero-a- const
para datos mutables es básicamente una promesa para otros humanos, no para el compilador. La eliminación de const
todo el lugar no conducirá a un comportamiento incorrecto del optimizador (AFAIK), a menos que intente modificar algo que el compilador coloca en la memoria de solo lectura (consulte a continuación las variables static const
). Si el compilador no puede ver la definición de una función al optimizar, tiene que suponer que descarta const
y modifica los datos a través de ese puntero (es decir, que la función no respeta la const
de sus arcos de puntero).
El compilador sabe que static const int foo = 15;
no obstante, no puede cambiar y alineará de forma confiable el valor incluso si pasa su dirección a funciones desconocidas. (Esta es la razón por static const int foo = 15;
cual static const int foo = 15;
no es más lento que #define foo 15
para un compilador optimizado. Los buenos compiladores lo optimizarán como un constexpr
siempre que sea posible).
Recuerde que restrict
es una promesa para el compilador de que las cosas a las que accede a través de ese puntero no se superponen con ninguna otra cosa . Si eso no es cierto, su función no necesariamente hará lo que espera. por ejemplo, no llame a foo_restrict(buf, buf, buf)
para operar en el lugar.
En mi experiencia (con gcc y clang), restrict
es principalmente útil en los punteros que almacenas. No está de más poner restrict
en los punteros de origen, también, pero normalmente obtienes todo el mejoramiento de asm posible al colocarlo solo en el (los) puntero (s) de destino, si todas las tiendas que tu función tiene son restrict
por punteros.
Si tiene alguna función llamada en su ciclo, restrict
en un puntero de fuente deja que clang (pero no gcc) evite una recarga. Vea estos casos de prueba en el explorador del compilador Godbolt , específicamente este:
void value_only(int); // a function the compiler can''t inline
int arg_pointer_valonly(const int *__restrict__ src)
{
// the compiler needs to load `*src` to pass it as a function arg
value_only(*src);
// and then needs it again here to calculate the return value
return 5 + *src; // clang: no reload because of __restrict__
}
gcc6.3 (dirigido a x86-64 SysV ABI) decide mantener src
(el puntero) en un registro conservado de llamada a través de la llamada de función, y volver a cargar *src
después de la llamada. O bien los algoritmos de gcc no detectaron esa posibilidad de optimización, o decidieron que no valía la pena, o los desarrolladores de gcc a propósito no lo implementaron porque creen que no es seguro. IDK que. Pero como clang lo hace, supongo que probablemente sea legal según el estándar C11.
clang4.0 optimiza esto para solo cargar *src
una vez, y mantener el valor en un registro preservado de llamada a través de la llamada de función. Sin restrict
, no hace esto, porque la función llamada podría (como un efecto secundario) modificar *src
través de otro puntero.
La persona que llama de esta función podría haber pasado la dirección de una variable global, por ejemplo . Pero cualquier modificación de *src
no sea a través del puntero src
violaría la promesa de restrict
al compilador. Como no pasamos src
a valonly()
, el compilador puede suponer que no modifica el valor.
El dialecto GNU de C permite usar __attribute__((pure))
o __attribute__((const))
para declarar que una función no tiene efectos secundarios , permitiendo esta optimización sin restrict
, pero no hay un equivalente portátil en ISO C11 (AFAIK). Por supuesto, permitir que la función se alinee (al colocarla en un archivo de encabezado o usar LTO) también permite este tipo de optimización, y es mucho mejor para las funciones pequeñas, especialmente si se llama dentro de los bucles.
Los compiladores generalmente son bastante agresivos con las optimizaciones que permite el estándar, incluso si sorprenden a algunos programadores y rompen algunos códigos inseguros que funcionaban. (C es tan portátil que muchas cosas son un comportamiento indefinido en el estándar base, la mayoría de las implementaciones buenas definen el comportamiento de muchas cosas que el estándar deja como UB). C no es un lenguaje donde es seguro lanzar código al compilador hasta hace lo que quiere, sin verificar que lo está haciendo de la manera correcta (sin desbordamientos de enteros firmados, etc.)
Si observa la salida de asma x86-64 para compilar su función (de la pregunta), puede ver fácilmente la diferencia. Lo puse en el explorador del compilador Godbolt .
En este caso, poner restrict
en a
es suficiente para permitir que el clang izar la carga de a[0]
, pero no gcc.
Con el resultado de float *restrict result
, tanto clang como gcc elevarán la carga.
p.ej
# gcc6.3, for foo with no restrict, or with just const float *restrict a
.L5:
vmovss xmm0, DWORD PTR [rsi]
vmulss xmm0, xmm0, DWORD PTR [rdx+rax*4]
vmovss DWORD PTR [rdi+rax*4], xmm0
add rax, 1
cmp rcx, rax
jne .L5
vs.
# gcc 6.3 with float *__restrict__ result
# clang is similar with const float *__restrict__ a but not on result.
vmovss xmm1, DWORD PTR [rsi] # outside the loop
.L11:
vmulss xmm0, xmm1, DWORD PTR [rdx+rax*4]
vmovss DWORD PTR [rdi+rax*4], xmm0
add rax, 1
cmp rcx, rax
jne .L11
En resumen, ponga __restrict__
en todos los punteros que garanticen que no se superpongan con otra cosa .
Por cierto, restrict
es solo una palabra clave en C. Algunos compiladores C ++ admiten __restrict__
o __restrict
como una extensión, por lo que debe #ifdef
en compiladores desconocidos.
Ya que