andreas
En gatomic.c
de simplista hay varias declaraciones de funciones que se ven así:
gboolean
(g_atomic_int_compare_and_exchange_full) (gint *atomic,
gint oldval,
gint newval,
gint *preval)
{
return g_atomic_int_compare_and_exchange_full (atomic, oldval, newval, preval);
}
¿Alguien puede explicar qué hace exactamente este código? Estoy confundido por varias cosas aquí:
-
El nombre de la función
g_atomic_int_compare_and_exchange_full
está entre paréntesis. ¿Cuál es el significado de esto? -
Aparentemente, el cuerpo de la función consiste en nada más que una llamada a la función en sí misma, por lo que se ejecutará para siempre y dará como resultado un desbordamiento de pila (juego de palabras).
No puedo darle ningún sentido a esta declaración de función. ¿Qué está pasando realmente aquí?
Gerhardh
- El nombre de la función g_atomic_int_compare_and_exchange_full está entre paréntesis. ¿Cuál es el significado de esto?
Poner el nombre de una función entre paréntesis evita cualquier expansión de macro en caso de que haya una función como macro con el mismo nombre.
Eso significa, g_atomic_int_compare_and_exchange_full(...)
utilizará la macro mientras (g_atomic_int_compare_and_exchange_full)(...)
utilizará la función.
¿Por qué se usa esto? No puede asignar una macro a un puntero de función. En ese caso, puede proporcionar la definición tal como la vio y luego puede usar
ptr = g_atomic_int_compare_and_exchange_full;
para usar la función en lugar de la macro.
- Aparentemente, el cuerpo de la función consiste en nada más que una llamada a la función en sí misma, por lo que se ejecutará para siempre y dará como resultado un desbordamiento de pila (juego de palabras).
Si miras en el encabezado asociado gatomic.h
verá que tal macro está definida. Y en el cuerpo de la función, no se usan corchetes alrededor del nombre de la función. Eso significa que se usa la macro y no es una recursión infinita.
-
Wow, no he hecho C ++ por un tiempo, pero la primera vez que me encontré con eso. Bueno saber. Normalmente no codifico de esa manera.
– J.Gwinner
18 de julio a las 5:46
-
¿Cuáles son las posibles razones por las que lo hicieron de esta manera en lugar de definir un
static inline
función en el archivo de encabezado o usando una función normal y habilitando LTO?– Paquete
18 de julio a las 15:55
-
@Pkkm LTO no es omnipresente incluso hoy en día y esto tampoco funcionaría para usuarios no simplistas de esta función cuando la usan desde simplismo compartido (que sería el caso en la mayoría de los sistemas Linux). Por qué no
static inline
– eso es algo de lo que estoy menos seguro; posiblemente para evitar niveles bajos de optimización que ralenticen demasiado las rutas activas.– val – decepcionado en SE
18 de julio a las 17:14
-
Evitará la expansión de funciones como macros, pero no las regulares.
– Ayxan Haqverdili
19 de julio a las 7:14
chrlg
La respuesta se da en los comentarios del código que ha vinculado.
La siguiente es una colección de macros del compilador para proporcionar acceso atómico a valores enteros y de tamaño de puntero.
Y relacionado con el comentario de @Gerhardh, por cierto.
int (fn)(int param){
fn(param);
}
Define una función fn
cuya acción es cualquier macro fn
se expande también.
El paréntesis alrededor de la primera aparición de fn
están ahí para evitar la expansión de este, lo que, obviamente, conduciría a un código inconsistente.
Ejemplo
sqr.c
#define sqr(x) x*x
int (sqr)(int x){
return sqr(x);
}
C Principal
#include <stdio.h>
extern int sqr(int);
int main(){
printf("%d\n", sqr(12));
}
Compilar con gcc -o main main.c sqr.c
Correr ./main
huellas dactilares 144
. Por supuesto.
Pero lo que es más interesante, main.c, después del preprocesamiento parece (gcc -E main.c
)
extern int sqr(int);
int main(){
printf("%d\n", sqr(12));
}
(Entonces, sqr
es una función aquí. Si fuera una macro, ya se habría expandido)
Y sqr.c
el preprocesamiento da
int (sqr)(int x){
return x*x;
}
Y que el punto principal: sqr
es una función, cuyo código es la macro expansión de sqr(x)
.
-
Por supuesto, cualquier programador de C probablemente definiría reflexivamente la macro como
#define sqr(x) ((x)*(x))
así por ejemplo5 / sqr(2)
ysqr(2+3)
funcionará como se esperaba.– Daniel Schepler
17 de julio a las 16:34
-
En efecto. Dudé en hacerlo. Pero no quería agregar otra cosa para explicar, ya que el objetivo no era decir “cómo codificar una macro”, sino “por qué se comporta así”. Así que, con cautela y un poco cobardemente, elegí un ejemplo para el cual no importa :D. Además, tenga en cuenta que, precisamente para esta pregunta, no es realmente un problema. Como macro, importa.
sqr(2+3)
terminaría como2+3*2+3
=11
en efecto. Pero para esta pregunta (y este ejemplo), el argumento no es2+3
, si la macro solo se usa para crear la función. Argumento de macro es símbolox
– chrlg
17 de julio a las 18:01
-
@DanielSchepler Y luego te muerde
sqr(++i)
– dobiwan
18 de julio a las 6:45
-
@dobiwan ¿cómo evitas ser mordido por
sqr(++i)
?– Zorgatone
18 de julio a las 12:30
-
@Zorgatone: más o menos, diría, con macro, no. Eso es lo que pediste si usas una macro: repetir la expresión, no el valor. O hace exactamente lo que se hace en esta respuesta (y en el código que muestra OP): define una función que usa una macro. Aquí cuando llamas a la función
sqr(++i)
++i se evalúa, se pasa a la función,sqr
en el que se llamax
. Y luegox*x
se calcula Así que al final,i=5; sqr(++i)
devuelve 36, no 42 :D. Porque aquí es solo una función. Y la macro solo se usa conx
como argumento, no++i
.– chrlg
18 de julio a las 13:33
Vlad de Moscú
Hay una macro con el nombre g_atomic_int_compare_and_exchange_full
definido en el encabezado gatomic.h
:
#define g_atomic_int_compare_and_exchange_full(atomic, oldval, newval, preval) \
(G_GNUC_EXTENSION ({ \
G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \
G_STATIC_ASSERT (sizeof *(preval) == sizeof (gint)); \
(void) (0 ? *(atomic) ^ (newval) ^ (oldval) ^ *(preval) : 1); \
*(preval) = (oldval); \
__atomic_compare_exchange_n ((atomic), (preval), (newval), FALSE, \
__ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) \
? TRUE : FALSE; \
}))
y hay una función con el mismo nombre.
Cuando el nombre está entre paréntesis como aquí,
gboolean
(g_atomic_int_compare_and_exchange_full) (gint *atomic,
gint oldval,
gint newval,
gint *preval)
entonces el compilador no puede considerarlo como una macro.
Es decir, tiene una definición de función dentro de la cual se usa una macro con el mismo nombre que el nombre de la función.
Aquí hay un programa de demostración:
#include <stdio.h>
#define from( x )( x ) * ( x )
int (from)( int x ) { return from( x ); }
int main( void )
{
printf( "%d\n", (from)( 10 ) );
}
Preste atención a que un declarador (incluido un declarador de función) puede estar entre paréntesis.
De la gramática C que define a los declaradores:
direct-declarator:
identifier
( declarator )
Aquí hay una declaración de una matriz bidimensional con declaradores adjuntos entre paréntesis:
int ( ( ( a )[10] )[10] );
Aunque los paréntesis evidentemente son redundantes.
-
“el compilador no puede considerarlo como una macro”: el compilador nunca considerará las cosas como una macro, en mi humilde opinión. En el momento de la compilación, el preprocesador ya ha eliminado todas las macros.
– Tomas Weller
17 de julio a las 6:09
-
@Thomas: Eso depende de si considera que el preprocesamiento es parte del compilador o no. Según ISO 9899, el preprocesamiento es uno de los (conceptual) etapas de traducción (y no creo que se mencione la palabra “compilador”), por lo que podría argumentarlo de cualquier manera.
–Toby Speight
18 de julio a las 7:44
Como han dicho otros, esto permite que algo se defina como una macro similar a una función y como una función real.
¿Por qué querrías hacer eso? Puedo ver varias razones.
- Compatibilidad al revés. El autor de una biblioteca puede cambiar de funciones reales a macros similares a funciones por razones de rendimiento o flexibilidad, pero aún desea mantener la compatibilidad con los archivos binarios existentes que llaman a las funciones reales.
- Compatibilidad con otros lenguajes de programación. Una macro escrita en C solo se puede usar de manera efectiva desde C y lenguajes como C ++ que son más o menos extensiones de C. Se puede llamar a una función real desde cualquier lenguaje que admita las convenciones de llamadas de C.
- Una función real se puede usar con punteros de función, una macro similar a una función no.
El código que proporcionó no es una definición de función completa, sino más bien un “envoltorio” o un “alias de función” para la función real. g_atomic_int_compare_and_exchange_full
. Analicemos lo que está sucediendo aquí:
- Alias de función: el código que proporcionó crea un alias para la función
g_atomic_int_compare_and_exchange_full
. El alias se crea utilizando paréntesis alrededor del nombre de la función y va seguido de la lista de parámetros y el cuerpo de la función. Esta técnica se usa comúnmente en macros para definir macros similares a funciones. En este caso, parece que la macro tiene un alias de la función real. - Llamada recursiva: tiene razón en que el código, tal como está, parece conducir a una llamada recursiva, lo que daría como resultado un desbordamiento de pila. Sin embargo, este fragmento de código probablemente sea parte de un contexto más amplio, y la definición real de la función `g_atomic_int_compare_and_exchange_full debería estar en otro lugar. La función real tendría una implementación adecuada y no sería una llamada recursiva a sí misma.
Para que esto quede más claro, la implementación real de g_atomic_int_compare_and_exchange_full debe definirse en otro lugar, y podría verse así:
gboolean g_atomic_int_compare_and_exchange_full(gint *atomic,
gint oldval,
gint newval,
gint *preval)
{
// Actual implementation of the atomic compare and exchange operation
// This function performs an atomic compare-and-exchange on the given integer value.
// It compares the value at the memory location "atomic" with "oldval", and if they are equal,
// it updates the value at "atomic" to "newval". The current value at "atomic" is returned in "preval".
// ... Implementation details ...
// (Actual atomic compare-and-exchange code)
// ...
return TRUE; // or FALSE, depending on whether the exchange was successful
}
Sin tener el código completo, es difícil saberlo. Poner el nombre de una función entre paréntesis evita cualquier expansión de macro en caso de que haya una función como macro con el mismo nombre. Dicho esto, esta podría ser una función contenedora para dicha macro. Pero eso es solo adivinanzas.
– Gerhardh
16 de julio a las 12:27
El código completo está aquí: gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gatomic.c
– Andreas
16 de julio a las 12:29
Una de mis cosas favoritas es cuando dos misterios aparentemente separados se responden.
– Daniel R. Collins
16 de julio a las 23:49
No estoy seguro de que decir algo que cause un desbordamiento de pila sea un juego de palabras solo porque lo publicaste en Stack Overflow…
– ScottishTapWater
18 de julio a las 10:06