¿Por qué el asterisco está antes del nombre de la variable, en lugar de después del tipo?

187

¿Por qué la mayoría de los programadores de C nombran variables como esta:

int *myVariable;

en lugar de esto:

int* myVariable;

Ambos son validos. Me parece que el asterisco es una parte del tipo, no una parte del nombre de la variable. ¿Alguien puede explicar esta lógica?

WBlasko
fuente
1
El segundo estilo parece más intuitivo en general, pero el primero es el camino a seguir para evitar errores relacionados con el tipo en el código. Si realmente está apegado al último estilo, siempre podría seguir typedefs, pero eso agregará una complejidad innecesaria, en mi humilde opinión.
Nube

Respuestas:

251

Son EXACTAMENTE equivalentes. Sin embargo, en

int *myVariable, myVariable2;

Parece obvio que myVariable tiene el tipo int * , mientras que myVariable2 tiene el tipo int . En

int* myVariable, myVariable2;

Puede parecer obvio que ambos son del tipo int * , pero eso no es correcto, ya que myVariable2tiene el tipo int .

Por lo tanto, el primer estilo de programación es más intuitivo.

luiscubal
fuente
135
quizás, pero no mezclaría y combinaría tipos en una declaración.
BobbyShaftoe
15
@BobbyShaftoe De acuerdo. Incluso después de leer cada argumento aquí, me quedo con int* someVarproyectos personales. Tiene más sentido
Alyssa Haroldsen
30
@Kupiakos Solo tiene más sentido hasta que aprenda la sintaxis de declaración de C basada en "las declaraciones siguen el uso". Las declaraciones usan exactamente la misma sintaxis que el uso de las variables del mismo tipo. Cuando se declara una matriz de enteros, que no se parece a: int[10] x. Esto simplemente no es la sintaxis de C. La gramática se analiza explícitamente como:, int (*x)y no como (int *) x, por lo que colocar el asterisco a la izquierda es simplemente engañoso y se basa en un malentendido de la sintaxis de la declaración C.
Pico
8
Corrección: por lo tanto, nunca debe declarar más de una variable en una sola línea. En general, no debe motivar un cierto estilo de codificación basado en otro estilo de codificación no relacionado, malo y peligroso.
Lundin
66
Es por eso que me quedo con una variable por declaración de puntero. No hay absolutamente ninguna confusión si lo haces en su int* myVariable; int myVariable2;lugar.
Patrick Roberts el
122

Si lo miras de otra manera, *myVariablees de tipo int, lo que tiene sentido.

biozinc
fuente
66
Esta es mi explicación favorita, y funciona bien porque explica las peculiaridades de la declaración de C en general, incluso la sintaxis de puntero de función repugnante y retorcida.
Benjamin Pollack
12
Es un poco ordenado, ya que puedes imaginar que no hay ningún tipo de puntero real. Solo hay variables que, cuando se referencian o desreferencian adecuadamente, le dan uno de los tipos primitivos.
biozinc
1
En realidad, '* myVariable' puede ser de tipo NULL. Para empeorar las cosas, podría ser una ubicación de memoria de memoria aleatoria.
qonf
44
qonf: NULL no es un tipo. myVariablepuede ser NULL, en cuyo caso *myVariablecausa una falla de segmentación, pero no hay ningún tipo NULL.
Daniel Roethlisberger
28
Este punto puede ser engañoso en dicho contexto: int x = 5; int *pointer = &x;porque sugiere que establecemos el int *pointeren algún valor, no en pointersí mismo.
rafalcieslak
40

Debido a que * en esa línea se une más estrechamente a la variable que al tipo:

int* varA, varB; // This is misleading

Como @Lundin señala a continuación, const agrega aún más sutilezas para pensar. Puede eludir esto completamente al declarar una variable por línea, que nunca es ambigua:

int* varA;
int varB;

El equilibrio entre el código claro y el código conciso es difícil de alcanzar: una docena de líneas redundantes int a;tampoco son buenas. Aún así, prefiero una declaración por línea y me preocupo por combinar el código más adelante.

ojrac
fuente
2
Bueno, el primer ejemplo engañoso es, en mi opinión, un error de diseño. Si pudiera, eliminaría esa forma de declaración de C por completo, y lo haría para que ambos sean del tipo int *.
Adam Bajger
1
"el * se une más estrechamente a la variable que al tipo" Este es un argumento ingenuo. Considere int *const a, b;. ¿De dónde viene el * "enlace"? El tipo de aes int* const, entonces, ¿cómo puedes decir que el * pertenece a la variable cuando es parte del tipo en sí?
Lundin
1
Específico a la pregunta, o que cubre todos los casos: elija uno. Tomaré una nota, pero este es otro buen argumento para mi última sugerencia: una declaración por línea reduce las oportunidades de estropear esto.
ojrac 05 de
36

Algo que nadie ha mencionado aquí hasta ahora es que este asterisco es en realidad el " operador de desreferencia " en C.

*a = 10;

La línea anterior no quiere decir que quiera asignar 10a a, significa que desea asignar 10a cualquier ubicación de memoria aseñala. Y nunca he visto a nadie escribiendo

* a = 10;

¿Tienes? Por lo tanto, el operador de desreferencia se escribe casi siempre sin un espacio. Esto es probablemente para distinguirlo de una multiplicación dividida en varias líneas:

x = a * b * c * d
  * e * f * g;

Aquí *esería engañoso, ¿no?

Bien, ahora, ¿qué significa realmente la siguiente línea:

int *a;

La mayoría de la gente diría:

Significa que aes un puntero a un intvalor.

Esto es técnicamente correcto, a la mayoría de la gente le gusta verlo / leerlo de esa manera y así es como los estándares C modernos lo definirían (tenga en cuenta que el lenguaje C en sí mismo es anterior a todos los estándares ANSI e ISO). Pero no es la única forma de verlo. También puede leer esta línea de la siguiente manera:

El valor desreferenciado de aes de tipo int.

De hecho, el asterisco en esta declaración también puede verse como un operador de desreferencia, lo que también explica su ubicación. Y ese aes un puntero que no se declara realmente, está implícito por el hecho de que lo único que puede desreferenciar realmente es un puntero.

El estándar C solo define dos significados para el *operador:

  • operador de indirección
  • operador de multiplicación

Y la indirección es solo un significado único, no hay un significado adicional para declarar un puntero, solo hay indirección, que es lo que hace la operación de desreferenciación, realiza un acceso indirecto, por lo que también dentro de una declaración como int *a;esta es un acceso indirecto ( *significa acceso indirecto) y, por lo tanto, la segunda declaración anterior está mucho más cerca del estándar que la primera.

Mecki
fuente
66
Gracias por salvarme de escribir otra respuesta aquí. Por cierto, generalmente leo int a, *b, (*c)();como algo así como "declarar los siguientes objetos como int: el objeto a, el objeto señalado por b, y el objeto devuelto de la función señalada por c".
Antti Haapala
1
El *en int *a;no es un operador, y no se eliminación de referencias a(que no está aún definido aún)
MM
1
@MM Por favor nombre la página y el número de línea de cualquier estándar ISO C donde este estándar dice que el asterisco puede ser algo más que multiplicación o indirección. Solo muestra la "declaración de puntero", por ejemplo, en ninguna parte define un tercer significado para asterisco (y no define ningún significado, eso no sería un operador). Oh, y en ninguna parte afirmé que algo está "definido", lo inventaste. O como dijo Jonathan Leffler, en el estándar C, * siempre es "gramática", no es parte de los especificadores de declaración enumerados (por lo que no es parte de una declaración, por lo tanto, debe ser un operador)
Mecki
1
@MM 6.7.6.1 sólo se muestra la declaración por ejemplo, tal y como he dicho, no da significado a *(sólo da un significado a la expresión total y no a la *dentro de la expresión!). Dice que "int a;" declara un puntero, lo que hace, nunca afirmó lo contrario, pero sin un significado dado *, leerlo como * el valor desreferenciado de aes un int sigue siendo totalmente válido, ya que tiene el mismo significado fáctico. Nada, realmente nada escrito en 6.7.6.1 contradeciría esa afirmación.
Mecki
2
No hay "expresión total". int *a;es una declaración, no una expresión. ano está desreferenciado por int *a;. ani siquiera existe en el momento en que *se está procesando. ¿Crees que int *a = NULL;es un error porque desreferencia un puntero nulo?
MM
17

Voy a salir en un miembro aquí y decir que hay una respuesta clara a esta pregunta , tanto para las declaraciones de variables y tipos de parámetros y retorno, y es que el asterisco debe ir al lado del nombre: int *myVariable;. Para apreciar por qué, mire cómo declara otros tipos de símbolos en C:

int my_function(int arg); para una función;

float my_array[3] para una matriz

El patrón general, denominado declaración después del uso , es que el tipo de símbolo se divide en la parte anterior al nombre, y las partes alrededor del nombre, y estas partes alrededor del nombre imitan la sintaxis que usaría para obtener un símbolo. valor del tipo a la izquierda:

int a_return_value = my_function(729);

float an_element = my_array[2];

y: int copy_of_value = *myVariable;.

C ++ arroja una llave en las obras con referencias, porque la sintaxis en el punto donde usa referencias es idéntica a la de los tipos de valor, por lo que podría argumentar que C ++ adopta un enfoque diferente a C. Por otro lado, C ++ conserva el mismo comportamiento de C en el caso de punteros, por lo que las referencias realmente se destacan como las extrañas a este respecto.

Injektilo
fuente
12

Eso es solo una cuestión de preferencia.

Cuando lee el código, distinguir entre variables y punteros es más fácil en el segundo caso, pero puede generar confusión cuando coloca variables y punteros de un tipo común en una sola línea (que a menudo se desalienta por las pautas del proyecto, porque disminuye la legibilidad).

Prefiero declarar punteros con su signo correspondiente junto al nombre del tipo, por ejemplo

int* pMyPointer;
Macbirdie
fuente
3
La pregunta es sobre C, donde no hay referencias.
Antti Haapala
Gracias por señalarlo, aunque la pregunta no era sobre punteros o referencias, sino sobre el formato del código, básicamente.
macbirdie
8

Un gran gurú dijo una vez: "Léelo a la manera del compilador, debes hacerlo".

http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835

De acuerdo, esto era sobre el tema de la colocación constante, pero la misma regla se aplica aquí.

El compilador lo lee como:

int (*a);

no como:

(int*) a;

Si tiene el hábito de colocar la estrella al lado de la variable, hará que sus declaraciones sean más fáciles de leer. También evita los ojos como:

int* a[10];

- Editar -

Explicar exactamente lo que quiero decir cuando digo que se analiza como int (*a), eso significa que se *une más estrechamente ade lo que lo hace int, de la misma manera que en la expresión se 4 + 3 * 7 3une más estrechamente 7de lo que lo hace 4debido a la mayor precedencia de *.

Con disculpas por el arte ascii, una sinopsis de la AST para analizar se int *ave más o menos así:

      Declaration
      /         \
     /           \
Declaration-      Init-
Secifiers       Declarator-
    |             List
    |               |
    |              ...
  "int"             |
                Declarator
                /       \
               /        ...
           Pointer        \
              |        Identifier
              |            |
             "*"           |
                          "a"

Como se muestra claramente, se *une más estrechamente aya que su antepasado común lo es Declarator, mientras que debe subir todo el árbol Declarationpara encontrar un antepasado común que involucre al int.

dgnuff
fuente
No, el compilador definitivamente lee el tipo como (int*) a.
Lundin
@Lundin Este es el gran malentendido que tienen los programadores de C ++ más modernos. Analizar una declaración de variable es algo como esto. Paso 1. Lee el primer token, que se convierte en el "tipo base" de la declaración. inten este caso. Paso 2. Lea una declaración, incluyendo cualquier tipo de decoración. *aen este caso. Paso 3 Lee el siguiente personaje. Si es una coma, consúmalo y vuelve al paso 2. Si el punto y coma se detiene. Si algo más arroja un error de sintaxis. ...
dgnuff 05 de
@Lundin ... Si el analizador lo leyó de la manera que usted sugiere, entonces podríamos escribir int* a, b;y obtener un par de punteros. El punto que estoy señalando es que se *une a la variable y se analiza con ella, no con el tipo para formar el "tipo base" de la declaración. Esa también es parte de la razón por la que se introdujeron typedefs para permitir typedef int *iptr; iptr a, b;crear un par de punteros. Al usar un typedef puede vincular el *a int.
dgnuff
1
... Ciertamente, el compilador "combina" el tipo base del Declaration-Specifiercon las "decoraciones" en el Declaratorpara llegar al tipo final para cada variable. Sin embargo, no "mueve" las decoraciones al especificador de Declaración de lo contrario int a[10], b;produciría resultados completamente ridículos, analiza int *a, b[10];como int *a , b[10] ;. No hay otra manera de describirlo que tenga sentido.
dgnuff
1
Sí, bueno, la parte importante aquí no es realmente la sintaxis o el orden del análisis del compilador, sino que el tipo de int*aes finalmente leído por el compilador como: " atiene tipo int*". A eso me refería con mi comentario original.
Lundin
3

Porque tiene más sentido cuando tienes declaraciones como:

int *a, *b;
Greg Rogers
fuente
55
Este es literalmente un ejemplo de mendigar la pregunta. No, no tiene más sentido de esa manera. "int * a, b" podría hacer que ambos sean punteros.
MichaelGG
1
@MichaelGG Tienes la cola moviendo al perro allí. Seguro K&R podría haber especificado que int* a, b;hizo bun puntero a int. Pero no lo hicieron. Y con buen motivo. Bajo su sistema propuesto, ¿cuál es el tipo de ben la siguiente declaración int* a[10], b;:?
dgnuff
2

Para declarar múltiples punteros en una línea, prefiero int* a, * b;que declare más intuitivamente "a" como un puntero a un número entero, y no mezcle estilos cuando también declare "b". Como alguien dijo, no declararía dos tipos diferentes en la misma declaración de todos modos.

heyitsluke
fuente
2

Personas que prefieren int* x; están tratando de forzar su código en un mundo ficticio donde el tipo está a la izquierda y el identificador (nombre) está a la derecha.

Digo "ficticio" porque:

En C y C ++, en el caso general, el identificador declarado está rodeado por la información de tipo.

Eso puede sonar loco, pero sabes que es verdad. Aquí hay unos ejemplos:

  • int main(int argc, char *argv[])significa " maines una función que toma un inty una matriz de punteros chary devuelve un int". En otras palabras, la mayoría de la información de tipo está a la derecha. Algunas personas piensan que las declaraciones de funciones no cuentan porque de alguna manera son "especiales". OK, intentemos una variable.

  • void (*fn)(int)significa fnes un puntero a una función que toma un inty no devuelve nada.

  • int a[10]declara 'a' como una matriz de 10 ints.

  • pixel bitmap[height][width].

  • Claramente, he seleccionado ejemplos que tienen mucha información de tipo a la derecha para aclarar mi punto. Hay muchas declaraciones donde la mayoría, si no todas, del tipo está a la izquierda, como struct { int x; int y; } center.

Esta sintaxis de declaración surgió del deseo de K&R de que las declaraciones reflejen el uso. La lectura de declaraciones simples es intuitiva, y la lectura de las más complejas se puede dominar aprendiendo la regla derecha-izquierda-derecha (a veces llamada la regla espiral o simplemente la regla derecha-izquierda).

C es lo suficientemente simple como para que muchos programadores de C adopten este estilo y escriban declaraciones simples como int *p.

En C ++, la sintaxis se volvió un poco más compleja (con clases, referencias, plantillas, clases de enumeración) y, como reacción a esa complejidad, verás un mayor esfuerzo para separar el tipo del identificador en muchas declaraciones. En otras palabras, es posible que vea más int* pdeclaraciones de estilo si revisa una gran franja de código C ++.

En cualquier idioma, siempre puede tener el tipo en el lado izquierdo de las declaraciones de variables al (1) nunca declarar múltiples variables en la misma declaración y (2) haciendo uso de typedefs (o declaraciones de alias, que, irónicamente, ponen el alias identificadores a la izquierda de los tipos). Por ejemplo:

typedef int array_of_10_ints[10];
array_of_10_ints a;
Adrian McCarthy
fuente
Pequeña pregunta, ¿por qué no puede anular (* fn) (int) significa "fn es una función que acepta un int y devuelve (void *)?"
Soham Dongargaonkar
1
@ a3y3: porque los paréntesis (*fn)mantienen el puntero asociado en fnlugar del tipo de retorno.
Adrian McCarthy
Entendido. Creo que es lo suficientemente importante como para agregarlo en su respuesta, sugeriré una edición pronto.
Soham Dongargaonkar
1
Creo que eso abarrota la respuesta sin agregar mucho valor. Esta es simplemente la sintaxis del lenguaje. La mayoría de las personas que preguntan por qué la sintaxis es así probablemente ya conocen las reglas. Cualquier otra persona que esté confundida sobre este punto puede ver la aclaración en estos comentarios.
Adrian McCarthy
1

Ya respondí a una pregunta similar en CP, y, como nadie mencionó eso, también tengo que señalar que C es un lenguaje de formato libre , sea cual sea el estilo que elija, está bien, mientras que el analizador puede hacer una distinción de cada token. Esta peculiaridad de C conducen a un tipo muy especial de concurso denominado C concurso de ofuscación .

Frankie_C
fuente
1

Muchos de los argumentos en este tema son completamente subjetivos y el argumento sobre "la estrella se une al nombre de la variable" es ingenuo. Aquí hay algunos argumentos que no son solo opiniones:


Los calificadores de tipo puntero olvidados

Formalmente, la "estrella" no pertenece al tipo ni al nombre de la variable, es parte de su propio elemento gramatical llamado puntero . La sintaxis formal de C (ISO 9899: 2018) es:

(6.7) declaración:
declaración-especificadores init-declarator-list opt ;

Donde los especificadores de declaración contienen el tipo (y el almacenamiento), y la lista de declaradores de inicio contiene el puntero y el nombre de la variable. Lo que vemos si diseccionamos aún más esta sintaxis de la lista de declarantes:

(6.7.6) declarator:
puntero opt direct-declarator
...
(6.7.6) puntero:
* type-qualifier-list opt
* type-qualifier-list opt puntero

Cuando un declarador es la declaración completa, un declarador directo es el identificador (nombre de la variable), y un puntero es la estrella seguida de una lista de calificador de tipo opcional que pertenece al puntero mismo.

Lo que hace que los diversos argumentos de estilo sobre "la estrella pertenece a la variable" sean inconsistentes, es que se han olvidado de estos calificadores de tipo puntero. int* const x, int *const xO int*const x?

Considere int *const a, b;, ¿cuáles son los tipos de ay b? Ya no es tan obvio que "la estrella pertenece a la variable" por más tiempo. Más bien, uno comenzaría a reflexionar sobre dóndeconst pertenece.

Definitivamente puede hacer un argumento sólido de que la estrella pertenece al calificador de tipo de puntero, pero no mucho más allá de eso.

La lista de calificadores de tipo para el puntero puede causar problemas a quienes usan el int *aestilo. Aquellos que usan punteros dentro de un typedef(que no deberíamos, ¡muy mala práctica!) Y piensan que "la estrella pertenece al nombre de la variable" tienden a escribir este error muy sutil:

    /*** bad code, don't do this ***/
    typedef int *bad_idea_t; 
    ...
    void func (const bad_idea_t *foo);

Esto se compila limpiamente. Ahora puede pensar que el código está hecho correctamente. ¡No tan! Este código es accidentalmente una corrección falsa constante.

El tipo de fooes en realidad int*const*: el puntero más externo se hizo de solo lectura, no los datos apuntados. Entonces dentro de esta función podemos hacer **foo = n;y cambiará el valor de la variable en la persona que llama.

Esto se debe a que en la expresión const bad_idea_t *foo, ¡ *no pertenece al nombre de la variable aquí! En pseudocódigo, esta declaración de parámetro debe leerse como const (bad_idea_t *) fooy no como (const bad_idea_t) *foo. La estrella pertenece al tipo de puntero oculto en este caso: el tipo es un puntero y un puntero calificado const se escribe como *const.

Pero entonces la raíz del problema en el ejemplo anterior es la práctica de ocultar punteros detrás de ay typedefno del *estilo.


En cuanto a la declaración de múltiples variables en una sola línea

Declarar múltiples variables en una sola línea es ampliamente reconocido como una mala práctica 1) . CERT-C lo resume muy bien como:

DCL04-C. No declare más de una variable por declaración

Simplemente leyendo el inglés, el sentido común está de acuerdo en que una declaración debería ser una declaración.

Y no importa si las variables son punteros o no. Declarar cada variable en una sola línea hace que el código sea más claro en casi todos los casos.

Entonces, la discusión sobre la confusión del programador int* a, bes mala. La raíz del problema es el uso de múltiples declaradores, no la colocación de *. Independientemente del estilo, deberías escribir esto en su lugar:

    int* a; // or int *a
    int b;

Otro argumento sólido pero subjetivo sería que dado int* ael tipo de aes sin dudaint* la estrella pertenece al calificador de tipo.

Pero básicamente mi conclusión es que muchos de los argumentos publicados aquí son solo subjetivos e ingenuos. Realmente no puede hacer un argumento válido para ninguno de los estilos: es realmente una cuestión de preferencia personal subjetiva.


1) CERT-C DCL04-C .

Lundin
fuente
De manera bastante divertida, si lees el artículo que vinculé, hay una pequeña sección sobre el tema. typedef int *bad_idea_t; void func(const bad_idea_t bar); Como el gran profeta Dan Saks enseña "Si siempre colocas lo constmás a la derecha posible, sin cambiar el significado semántico", esto cesa por completo ser un problema También hace que sus constdeclaraciones sean más consistentes para leer. "Todo a la derecha de la palabra const es lo que es const, todo a la izquierda es su tipo". Esto se aplicará a todos los concursos en una declaración. Pruébelo conint const * * const x;
dgnuff
-1

Considerar

int *x = new int();

Esto no se traduce en

int *x;
*x = new int();

En realidad se traduce en

int *x;
x = new int();

Esto hace que la int *xnotación sea algo inconsistente.

Urquhart
fuente
newes un operador de C ++. Su pregunta está etiquetada "C" y no "C ++".
Sapphire_Brick