c gcc linux-kernel c-preprocessor

Abstruse#define macro encontrada en Linux kernel source



gcc linux-kernel (1)

Lo que se ve entre la apertura ({ y cierre }) es una expresión de enunciado , una característica no estándar del compilador GCC, que permite incrustar sentencias compuestas en expresiones C. El resultado de dicha expresión de declaración es la última declaración de expresión dentro de ({}) . En tu caso, eso sería &__get_cpu_var(var) .

El operador & se aplica al resultado de __get_cpu_var(var) subexpression. Eso implica que __get_cpu_var devuelve un valor l. Si esto es realmente C, entonces __get_cpu_var también debe ser una macro, ya que en el lenguaje C las funciones no pueden devolver lvalues.

El operador & produce un puntero (el resultado de la expresión de la declaración completa), que luego es desreferenciado por un operador * presente al principio de la definición de macro anterior. Entonces, la macro anterior es esencialmente equivalente a la expresión *&__get_cpu_var(var) .

Algunos podrían preguntar por qué se implementa como *&__get_cpu_var(var) y no solo __get_cpu_var(var) . Esto se hace de esa manera para preservar la lvaloriedad del resultado de __get_cpu_var(var) . El resultado de la expresión de declaración siempre es un valor r, incluso si el último stetement dentro de ({}) fue un valor l. Con el fin de preservar la validez del resultado, se usa el conocido *& truco.

Este truco no está limitado a expresiones de enunciados GCC de ninguna manera. Se usa con relativa frecuencia en la programación común de C cotidiano. Por ejemplo, imagina que tienes dos variables

int a, b;

y desea escribir una expresión que devuelva a o b como un valor l (supongamos que queremos asignarle 42 ) según la selección de la variable selector. Un intento ingenuo podría verse de la siguiente manera

(select ? a : b) = 42;

Esto no funcionará, ya que en lenguaje C, el operador ?: Pierde la validez de sus operandos. El resultado es un valor r, que no se puede asignar a. En esta situación, el *& truco viene al rescate

*(select ? &a : &b) = 42;

y ahora funciona según lo previsto.

Así es exactamente y por qué la definición de macro del póster original contiene una aplicación aparentemente redundante de * y & . Por eso puede usar la macro get_cpu_var arriba a cada lado de un reconocimiento

something = get_cpu_var(something); get_cpu_var(something) = something;

sin ese truco, solo get_cpu_var usar get_cpu_var en el lado derecho.

En lenguaje C ++, el mismo efecto se logra mediante el uso de referencias . En C no tenemos referencias, así que usamos trucos como este en su lugar.

El marcro get_cpu_var que se define a continuación

29 #define get_cpu_var(var) (*({ / 30 extern int simple_identifier_##var(void); / 31 preempt_disable(); / 32 &__get_cpu_var(var); }))

parece incomprensible. Supongo que fue un tipo de macro función que devuelve un puntero variable (basado en el asterisco) o es una especie de puntero a la función. ¿Estoy cerca de él? ¿Alguien podría iluminarme?