2010-07-15 14 views
22

La siguiente declaración en C:Sintaxis del puntero en C: ¿por qué * solo se aplica a la primera variable?

int* a, b; 

declarará a como tipo int* y b como tipo int. Soy muy consciente de esta trampa, pero lo que quiero saber es por qué funciona de esta manera. ¿Por qué no declara también b como int*, como la mayoría de las personas esperaría intuitivamente? En otras palabras, ¿por qué se aplica * al nombre de la variable, en lugar del tipo?

Claro que podría escribir de esta manera a ser más coherente con la forma en que realmente obras:

int *a, b; 

Sin embargo, yo y todos los que he hablado pensar en términos de una es de tipo " puntero a int ", en lugar de a es un puntero a algunos datos y el tipo de esos datos es" int ".

¿Fue simplemente una mala decisión de los diseñadores de C o hay alguna buena razón por la cual se analiza de esta manera? Estoy seguro de que la pregunta ha sido respondida antes, pero parece que no puedo encontrarla usando la búsqueda.

+7

Para ser honesto, si el lenguaje C hiciera que el '*' modifique el tipo, la gente preguntaría "¿por qué' int * a, b; 'no me da un puntero y un entero?". – bta

+2

@bta, sí, lo haríamos, pero creo que habría confundido a menos personas. De todos modos, el punto es no discutir sobre la decisión que se tomó hace 40 años, es solo para entender si hay razones para eso que no conozco. – EMP

+0

Es posible que desee leer esto http://c-faq.com/decl/charstarws.html – Amarghosh

Respuesta

17

Hay una página web en The Development of the C Language que dice: "La sintaxis de estas declaraciones refleja la observación de que i, * pi y ** ppi producen un tipo int cuando se usan en una expresión". Busque esa frase en la página para encontrar la sección relevante que habla sobre esta pregunta.

9

Puede haber una razón histórica adicional, pero siempre me he entendido de esta manera:

Una declaración, un tipo.

Si a, b, c, y d deben ser del mismo tipo aquí:

int a, b, c, d; 

Entonces todo en la línea debe un número entero también.

int a, *b, **c, ***d; 

Los 4 números enteros:

  1. un
  2. * b
  3. ** c
  4. *** d

Se puede estar relacionado con la precedencia de operadores, también, o puede haber sido en algún momento en el pasado.

+0

La pregunta no fue cómo la entendió. –

+0

@georg: es solo una redacción diferente de las dos anteriores. ¿Cómo lo entendiste? – eruciform

+3

@Georg, eruciform es correcto. De K & R C, "La declaración del puntero ip,' int * ip', está pensada como una mnemotécnica; dice que la expresión '* ip' es un' int'. La sintaxis de la declaración para una variable imita la sintaxis de expresiones en las que puede aparecer la variable. Este * es * el motivo de la sintaxis. –

2

El * modifica el nombre de la variable, no el especificador de tipo. Esto se debe principalmente a la forma en que se analiza el *. Tome estas declaraciones:

char* x; 
char *x; 

Esas declaraciones son equivalentes. El operador * necesita estar entre el especificador de tipo y el nombre de la variable (se trata como un operador infijo), pero puede ir a ambos lados del espacio. Teniendo en cuenta esto, la declaración

int* a, b; 

no haría b un puntero, porque no hay * adyacente a la misma. El * solo opera en los objetos a cada lado.

Además, piénselo de esta manera: cuando escribe la declaración int x;, indica que x es un número entero. Si y es un puntero a un número entero, entonces *y es un número entero. Cuando escribe int *y;, indica que *y es un número entero (que es lo que desea). En la declaración char a, *b, ***c;, está indicando que la variable a, el valor desreferenciado de b, y el valor triplemente desreferenciado de c son todos del tipo char. Al declarar las variables de esta forma, el uso del operador de estrella (casi) es coherente con la desreferenciación.

Estoy de acuerdo con que tendría más sentido que sea al revés. Para evitar esta trampa, me convertí en una regla siempre para declarar punteros en una línea por sí mismos.

+2

Ese es el status-quo, pero * ¿por qué es así? –

31

Las declaraciones C se escribieron de esta manera para que "la declaración espeje use". Es por eso que declarar un arreglo de esta manera:

int a[10]; 

que iban a tener lugar la regla que usted propone, donde es siempre

type identifier, identifier, identifier, ... ; 

... entonces las matrices lógicamente tendría que ser declarado como esto :

int[10] a; 

lo cual está bien, pero no refleja cómo se utiliza a. Tenga en cuenta que esto es válido para las funciones, también - declaramos funciones de la siguiente manera:

void foo(int a, char *b); 

en lugar de

void(int a, char* b) foo; 

En general, los "espejos de declaración de uso" regla significa que sólo tiene que recordar una serie de las reglas de asociatividad, que se aplican a los operadores como *, [] y () cuando usa el valor, y los tokens correspondientes en los declaradores como *, [] y ().


Después de pensar un poco más, creo que es también vale la pena señalar que la ortografía "puntero a int" como "int*" es sólo una consecuencia de "espejos de declaración de uso" de todos modos. Si fuera a usar otro estilo de declaración, probablemente tendría más sentido deletrear "puntero a int" como "&int", o algo completamente diferente como "@int".

+1

Hmm, esto no parece una buena razón para mí. I * do * quiere declarar arreglos como 'int [10]', que es como lo hacen Java y C#. ¿Hay otros ejemplos en la sintaxis C del uso de reflejo de declaración? – EMP

+0

@Evgeny, C es un lenguaje muy diferente de Java. Una matriz de Java es totalmente diferente. La variable de matriz contiene solo una referencia modificable a una matriz. Una variable de matriz C * es * la matriz. Otro ejemplo de "uso de los espejos de declaración" es una declaración de una función que devuelve un puntero a un número entero. 'int * foo (int barra);'. Esto refleja el hecho de que '* foo (3)' es un int. Le sugiero que lea K & R C 2nd ed., Donde se explica esto. –

+1

@Evgeny: el otro ejemplo más obvio es declaraciones de función, que se escriben para parecerse a la llamada de función. – caf

1

Porque si la declaración

int* a, b; 

declarara b como un puntero, lo que no tendría forma de declarar

int* a; 
int b; 

en una sola línea.

Por otro lado, se puede hacer

int*a, *b; 

para conseguir lo que desea.

Piénsalo así: la forma en que es ahora sigue siendo la forma más concisa y, sin embargo, única de hacerlo. Eso es lo que C es principalmente acerca :)

+1

Sí, bueno, del mismo modo, no se puede declarar un int y un flotador en la misma línea, ¿y qué? En realidad, no puede descercarlos en la misma * declaración *, pero podría tener una * línea * que dijera 'int * a; int b; '- no hay problema. – EMP

2

supongo que está relacionada con la sintaxis de declaración completa de modificadores de tipo:

int x[20], y; 
int (*fp)(), z; 

En estos ejemplos, se siente mucho más obvio que los modificadores solamente están afectando a una de las declaraciones Una suposición es que una vez que K & R decidió diseñar modificadores de esta manera, se consideró "correcto" que los modificadores solo afectaran a una declaración.

En una nota lateral, recomendaría simplemente limitación a una variable por declaración:

int *x; 
int y; 
+0

Sí, * es * más obvio aquí, pero en realidad, ¿quién diablos declara un puntero de función y un int en la misma declaración? :) – EMP

+1

@Evgeny - te estás perdiendo el punto. Para la sintaxis del puntero de función, es fácil entender por qué los modificadores solo afectan a una variable. Si el lenguaje era inconsistente y algunos modificadores afectan una variable, otros modificadores afectaron a todas las variables, eso sería un mal diseño. –

0

sólo puedo adivinar por qué habría que repetir el * para cada nombre de la variable mencionada en una línea para hacer ellos todos los punteros. ¿Tal vez esto se deba a una decisión sobre la coherencia del lenguaje? Permítanme ilustrar esto con un ejemplo:


Supongamos que tiene una función foo declara como sigue:

int foo() { ... } 

Supongamos también que desea declarar dos punteros de función para esta función:

int (*fooptr1, fooptr2)(); 
// this doesn't work; and even if it did, what would the syntax possibly 
// look like to initializing them to function foo? 
// int (*fooptr1 = foo, fooptr2 = foo)() ? 

int (*fooptr1)() = foo, (*fooptr2)() = foo; 
// this works. 

En este caso, simplemente tiene que repetir toda la declaración de tipo de b otras variables, y no se puede equivocar porque no hay otra forma de hacerlo (dada la sintaxis de la declaración C tal como está).


Ahora, tal vez se pensó, si hay casos en que la declaración de tipo tiene que ser repetido para todas las variables, tal vez esto sólo debería ser el caso general.

(no se olvide que estoy aquí sólo una suposición.)

1

Considerar la declaración:

int *a[10]; 
int (*b)[10]; 

La primera es una serie de diez punteros a enteros, el segundo es un puntero a una matriz de diez enteros.

Ahora, si el * se adjuntó a la declaración de tipo, no sería sintácticamente válido poner un paréntesis entre ellos. Entonces tendrías que encontrar otra manera de diferenciar entre las dos formas.

+3

"Cabe señalar que la declaración de un puntero a una matriz no es tan útil" ¿Qué? Con 'int (* b) [10]', puede asignar 'b' para señalar, p. Ej. un elemento arbitrario de 'int ar [ROW_COUNT] [10]', y la aritmética del puntero operará por fila (por ejemplo, después de 'ar + = 1',' ar' se incrementa en una fila). Explique cómo hacerlo con un "simple puntero" (no estoy seguro de qué significa "simple" en realidad). –

+0

Tienes razón, estaba pensando más sobre el uso de ese puntero como parámetro y no consideré el caso que mencionaste. Corrigiendo ahora. –

Cuestiones relacionadas