Por que o asterisco está antes do nome da variável e não após o tipo?


187

Por que a maioria dos programadores C nomeia variáveis ​​como esta:

int *myVariable;

em vez de assim:

int* myVariable;

Ambos são válidos. Parece-me que o asterisco faz parte do tipo, não parte do nome da variável. Alguém pode explicar essa lógica?


1
O segundo estilo parece mais intuitivo em geral, mas o primeiro é o caminho a seguir para evitar bugs relacionados ao tipo no código. Se você está realmente apegado ao último estilo, sempre pode ir com ele typedefs, mas isso adicionará complexidade desnecessária, IMHO.
Cloud

Respostas:


251

Eles são EXATAMENTE equivalentes. No entanto, em

int *myVariable, myVariable2;

Parece óbvio que myVariable possui o tipo int * , enquanto myVariable2 possui o tipo int . No

int* myVariable, myVariable2;

pode parecer óbvio que ambos são do tipo int * , mas isso não está correto como o myVariable2tipo int .

Portanto, o primeiro estilo de programação é mais intuitivo.


135
talvez, mas eu não misturasse e combinasse tipos em uma declaração.
BobbyShaftoe

15
@BobbyShaftoe concordou. Mesmo depois de ler todos os argumentos aqui, continuo com int* someVarprojetos pessoais. Faz mais sentido.
Alyssa Haroldsen

30
@Kupiakos Só faz mais sentido até você aprender a sintaxe da declaração de C com base em "declarações seguem o uso". As declarações usam exatamente a mesma sintaxe que o uso das variáveis ​​do mesmo tipo. Quando você declarar um array de inteiros, ele não se parece com: int[10] x. Isso simplesmente não é a sintaxe do C. A gramática analisa explicitamente como: int (*x)e não como (int *) x, portanto, colocar o asterisco à esquerda é simplesmente enganoso e baseado em um mal-entendido da sintaxe da declaração C.
Peaker

8
Correção: portanto, você nunca deve declarar mais de uma variável em uma única linha. Em geral, você não deve motivar um certo estilo de codificação com base em outro estilo de codificação não relacionado, ruim e perigoso.
Lundin

6
É por isso que me apego a uma variável por declaração de ponteiro. Não há absolutamente nenhuma confusão se você fizer isso int* myVariable; int myVariable2;.
Patrick Roberts

122

Se você olhar de outra maneira, *myVariableé do tipo int, o que faz algum sentido.


6
Esta é a minha explicação favorita e funciona bem porque explica as peculiaridades da declaração de C em geral - até mesmo a sintaxe repulsiva e gnarly do ponteiro de função.
Benjamin Pollack

12
É meio legal, já que você pode imaginar que não há nenhum tipo de ponteiro real. Existem apenas variáveis ​​que, quando adequadamente referenciadas ou desreferenciadas, fornecem um dos tipos primitivos.
biozinc 29/12/08

1
Na verdade, '* myVariable' pode ser do tipo NULL. Para piorar a situação, poderia ser apenas um local de memória de memória aleatória.
qonf

4
qonf: NULL não é um tipo. myVariablepode ser NULL, nesse caso, *myVariablecausa uma falha de segmentação, mas não existe um tipo NULL.
Daniel Roethlisberger

28
Esse ponto pode ser enganoso nesse contexto: int x = 5; int *pointer = &x;porque sugere que definimos o int *pointercom algum valor, não o pointerpróprio.
Rafalcieslak

40

Como o * nessa linha se liga mais estreitamente à variável do que ao tipo:

int* varA, varB; // This is misleading

Como o @Lundin aponta abaixo, o const adiciona ainda mais sutilezas para pensar. Você pode evitar isso declarando uma variável por linha, o que nunca é ambíguo:

int* varA;
int varB;

É difícil encontrar o equilíbrio entre código claro e código conciso - uma dúzia de linhas redundantes int a;também não é boa. Ainda assim, eu padronizo uma declaração por linha e me preocupo com a combinação de código posteriormente.


2
Bem, o primeiro exemplo enganoso é, aos meus olhos, um erro de design. Se eu pudesse, removeria completamente esse modo de declaração de C e o tornaria ambos do tipo int *.
Adam Bajger 29/05/19

1
"o * liga-se mais à variável do que ao tipo" Este é um argumento ingênuo. Considere int *const a, b;. Onde o * "liga"? O tipo de aé int* const, então como você pode dizer que o * pertence à variável quando faz parte do próprio tipo?
Lundin

1
Específico à pergunta ou abrangendo todos os casos: escolha um. Vou fazer uma anotação, mas esse é outro bom argumento para minha última sugestão: uma declaração por linha reduz as oportunidades de estragar tudo.
ojrac

36

Algo que ninguém mencionou aqui até agora é que esse asterisco é realmente o " operador de desreferência " em C.

*a = 10;

A linha acima não significa que eu deseja atribuir 10a a, isso significa que eu quero atribuir 10a qualquer local de memória aaponta para. E eu nunca vi ninguém escrevendo

* a = 10;

você já? Portanto, o operador de desreferência é quase sempre escrito sem espaço. Provavelmente é para distingui-lo de uma multiplicação dividida em várias linhas:

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

Aqui *eseria enganoso, não seria?

Ok, agora o que a seguinte linha realmente significa:

int *a;

A maioria das pessoas diria:

Isso significa que aé um ponteiro para um intvalor.

Isso é tecnicamente correto, a maioria das pessoas gosta de ver / ler dessa maneira e é assim que os padrões C modernos o definiriam (observe que a própria linguagem C é anterior a todos os padrões ANSI e ISO). Mas não é a única maneira de ver isso. Você também pode ler esta linha da seguinte maneira:

O valor referenciado do aé do tipo int.

Portanto, o asterisco nesta declaração também pode ser visto como um operador de desreferência, o que também explica sua localização. E esse aé um ponteiro que não é realmente declarado, está implícito pelo fato de que a única coisa que você pode realmente desreferenciar é um ponteiro.

O padrão C define apenas dois significados para o *operador:

  • operador indireto
  • operador de multiplicação

E a indireção é apenas um único significado, não há um significado extra para declarar um ponteiro, há apenas a indireção, que é o que a operação de desreferência faz, ela executa um acesso indireto, portanto, também dentro de uma declaração como int *a;essa é um acesso indireto ( *significa acesso indireto) e, portanto, a segunda declaração acima está muito mais próxima do padrão do que a primeira.


6
Obrigado por me salvar de escrever mais uma resposta aqui. Entre, normalmente, leio o int a, *b, (*c)();como algo como "declare os seguintes objetos como int: o objeto a, o objeto apontado por be o objeto retornado da função apontada por c".
Antti Haapala

1
A *em int *a;não é um operador, e não é dereferencing a(que não é mesmo ainda definidos)
MM

1
@MM Nomeie a página e o número da linha de qualquer padrão ISO C em que este padrão diga que o asterisco pode ser algo além de multiplicação ou indireção. Ele mostra apenas "declaração de ponteiro" por exemplo, em nenhum lugar define um terceiro significado para asterisco (e não define nenhum significado, que não seria um operador). Ah, e em nenhum lugar afirmei que algo está "definido", você inventou isso. Ou como Jonathan Leffler colocou, no padrão C, * sempre é "gramática", não faz parte dos especificadores de declaração listados (portanto, não faz parte de uma declaração, portanto, deve ser um operador)
Mecki

1
@MM 6.7.6.1 só mostra a declaração por exemplo, exatamente como eu disse, não dá nenhum significado para *(ele só dá um significado à expressão total, e não para o *dentro da expressão!). Diz que "int a;" declara um ponteiro, o que ele faz, nunca reivindicou o contrário, mas sem um significado atribuído *, a leitura como * o valor não referenciado de aé um int ainda é totalmente válido, pois tem o mesmo significado factual. Nada, realmente nada escrito em 6.7.6.1 contradizia essa afirmação.
Mecki #

2
Não há "expressão total". int *a;é uma declaração, não uma expressão. anão é desreferenciado por int *a;. aainda não existe no momento em que *está sendo processado. Você acha que int *a = NULL;é um bug porque desreferencia um ponteiro nulo?
MM

17

Vou sair em um membro aqui e dizer que não há uma resposta direta a esta pergunta , tanto para declarações de variáveis e para tipos de parâmetro e de retorno, o que é que o asterisco deve ir ao lado do nome: int *myVariable;. Para entender o motivo, veja como você declara outros tipos de símbolo em C:

int my_function(int arg); para uma função;

float my_array[3] para uma matriz.

O padrão geral, denominado declaração após o uso , é que o tipo de um símbolo é dividido na parte anterior ao nome e nas partes ao redor do nome, e essas partes ao redor do nome imitam a sintaxe que você usaria para obter uma valor do tipo à esquerda:

int a_return_value = my_function(729);

float an_element = my_array[2];

e: int copy_of_value = *myVariable;.

O C ++ lança uma chave inglesa nos trabalhos com referências, porque a sintaxe no ponto em que você usa as referências é idêntica à dos tipos de valor, portanto, você pode argumentar que o C ++ adota uma abordagem diferente de C. Por outro lado, o C ++ mantém o mesmo comportamento de C no caso de ponteiros, então as referências realmente são as mais estranhas a esse respeito.


12

Isso é apenas uma questão de preferência.

Quando você lê o código, é mais fácil distinguir entre variáveis ​​e ponteiros no segundo caso, mas pode causar confusão quando você coloca variáveis ​​e ponteiros de um tipo comum em uma única linha (o que muitas vezes é desencorajado pelas diretrizes do projeto, porque diminui a legibilidade).

Prefiro declarar ponteiros com o sinal correspondente ao lado do nome do tipo, por exemplo

int* pMyPointer;

3
A questão é sobre C, onde não há referências.
Antti Haapala

Obrigado por apontar isso, embora a pergunta não fosse sobre ponteiros ou referências, mas sobre formatação de código, basicamente.
31917 Macbirdie

8

Um grande guru disse uma vez "Leia da maneira do compilador, você deve."

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

Concedido isso foi sobre o tópico const const, mas a mesma regra se aplica aqui.

O compilador lê como:

int (*a);

não como:

(int*) a;

Se você adquirir o hábito de colocar a estrela ao lado da variável, facilitará a leitura das suas declarações. Também evita problemas oculares, como:

int* a[10];

- Editar -

Explicar exatamente o que quero dizer quando digo que é analisado como int (*a), significa que isso *se liga mais firmemente ado que a isso int, da mesma maneira que na expressão 4 + 3 * 7 3se liga mais firmemente 7do que 4devido à maior precedência de *.

Com desculpas pela arte ascii, uma sinopse do AST para análise int *aé mais ou menos assim:

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

Como é claramente mostrado, *vincula-se mais firmemente, ajá que o ancestral comum é Declarator, enquanto você precisa subir a árvore Declarationpara encontrar um ancestral comum que envolva o int.


Não, o compilador definitivamente lê o tipo como (int*) a.
Lundin

@Lundin Este é o grande mal-entendido que a maioria dos programadores de C ++ modernos tem. A análise de uma declaração de variável é mais ou menos assim. Etapa 1. Leia o primeiro token, que se torna o "tipo base" da declaração. intnesse caso. Etapa 2. Leia uma declaração, incluindo qualquer tipo de decoração. *anesse caso. Etapa 3 Leia o próximo caractere. Se vírgula, consuma-a e volte para a etapa 2. Se o ponto-e-vírgula parar. Se alguma outra coisa gerar um erro de sintaxe. ...
dgnuff

@Lundin ... Se o analisador o ler da maneira que você está sugerindo, poderemos escrever int* a, b;e obter um par de indicadores. O que estou dizendo é que o *vínculo com a variável é analisado com ela, não com o tipo para formar o "tipo base" da declaração. Isso também faz parte do motivo pelo qual os typedefs foram introduzidos para permitir a typedef int *iptr; iptr a, b;criação de alguns ponteiros. Usando um typedef, você pode vincular o *ao int.
precisa

1
... Certamente, o compilador "combina" o tipo base do Declaration-Specifiercom as "decorações" Declaratorpara chegar ao tipo final de cada variável. No entanto, ele não "move" as decorações para o especificador de Declaração, caso contrário int a[10], b;, produziria resultados completamente ridículos. Ele analisa int *a, b[10];como int *a , b[10] ;. Não há outra maneira de descrevê-lo que faça sentido.
dgnuff

1
Sim, bem, a parte importante aqui não é realmente a sintaxe ou a ordem da análise do compilador, mas o tipo de int*ano final é lido pelo compilador como: " ahas type int*". Foi isso que eu quis dizer com o meu comentário original.
Lundin

3

Porque faz mais sentido quando você tem declarações como:

int *a, *b;

5
Este é literalmente um exemplo de se fazer a pergunta. Não, não faz mais sentido assim. "int * a, b" também poderia fazer os dois ponteiros.
18715 Michael

1
@MichaelGG Você tem a cauda abanando o cachorro lá. Claro que K&R poderia ter especificado que int* a, b;fez bum ponteiro para int. Mas eles não fizeram. E por uma boa razão. No sistema proposto, qual é o tipo da bseguinte declaração int* a[10], b;:?
dgnuff

2

Para declarar vários ponteiros em uma linha, prefiro o int* a, * b;que mais intuitivamente declara "a" como ponteiro para um número inteiro e não combina estilos ao declarar "b". Como alguém disse, eu não declararia dois tipos diferentes na mesma declaração.


2

As pessoas que preferem int* x;estão tentando forçar seu código a um mundo fictício, onde o tipo está à esquerda e o identificador (nome), à ​​direita.

Eu digo "fictício" porque:

Em C e C ++, no caso geral, o identificador declarado é cercado pelas informações de tipo.

Pode parecer loucura, mas você sabe que é verdade. aqui estão alguns exemplos:

  • int main(int argc, char *argv[])significa " mainé uma função que leva um inte uma matriz de ponteiros para chare retorna um int". Em outras palavras, a maioria das informações de tipo está à direita. Algumas pessoas pensam que as declarações de função não contam porque são de alguma forma "especiais". OK, vamos tentar uma variável.

  • void (*fn)(int)meios fné um ponteiro para uma função que recebe inte não retorna nada.

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

  • pixel bitmap[height][width].

  • Claramente, escolhi exemplos que têm muitas informações de tipo à direita para expressar minha opinião. Existem muitas declarações em que a maioria - se não todos - do tipo está à esquerda, como struct { int x; int y; } center.

Essa sintaxe da declaração surgiu do desejo da K&R de fazer com que as declarações refletissem o uso. A leitura de declarações simples é intuitiva, e a leitura de declarações mais complexas pode ser dominada pelo aprendizado da regra direita-esquerda-direita (às vezes chamada de regra espiral ou apenas da regra direita-esquerda).

C é simples o suficiente para que muitos programadores adotem esse estilo e escrevam declarações simples como int *p.

No C ++, a sintaxe ficou um pouco mais complexa (com classes, referências, modelos, classes enum) e, como uma reação a essa complexidade, você verá mais esforço para separar o tipo do identificador em muitas declarações. Em outras palavras, você poderá ver mais int* pdeclarações de estilo se verificar uma grande faixa de código C ++.

Em qualquer idioma, você sempre pode ter o tipo no lado esquerdo das declarações de variáveis (1) nunca declarando várias variáveis ​​na mesma declaração e (2) fazendo uso de typedefs (ou alias), que, ironicamente, colocam o alias identificadores à esquerda dos tipos). Por exemplo:

typedef int array_of_10_ints[10];
array_of_10_ints a;

Questão pequena, por que não pode void (* fn) (int) média "fn é uma função que aceita um int e retorna (void *)?
Soham Dongargaonkar

1
@ a3y3: porque os parênteses (*fn)mantêm o ponteiro associado fnao tipo de retorno, e não ao tipo.
Adrian McCarthy

Entendi. Eu acho que é importante o suficiente para ser adicionado à sua resposta. Sugerimos uma edição em breve.
Soham Dongargaonkar

1
Eu acho que isso atrapalha a resposta sem agregar muito valor. Esta é simplesmente a sintaxe do idioma. A maioria das pessoas perguntando por que a sintaxe é assim provavelmente já conhece as regras. Qualquer pessoa que esteja confusa sobre esse ponto pode ver o esclarecimento nesses comentários.
Adrian McCarthy

1

Eu já respondi a uma pergunta semelhante no PC e, porque ninguém mencionou isso, também aqui eu tenho que apontar que C é uma linguagem de formato livre , qualquer que seja o estilo escolhido, enquanto o analisador pode fazer uma distinção de cada token. Esta peculiaridade de C levar a um tipo muito especial de concurso chamado concurso de ofuscação C .


1

Muitos dos argumentos deste tópico são claramente subjetivos e o argumento sobre "a estrela se liga ao nome da variável" é ingênuo. Aqui estão alguns argumentos que não são apenas opiniões:


Os qualificadores de tipo de ponteiro esquecidos

Formalmente, a "estrela" não pertence ao tipo nem ao nome da variável; faz parte de seu próprio item gramatical chamado ponteiro . A sintaxe C formal (ISO 9899: 2018) é:

(6.7) declaração:
especificadores de declaração init-declarator-list opt ;

Onde os especificadores de declaração contêm o tipo (e o armazenamento) e a lista de declaradores init contém o ponteiro e o nome da variável. Que vemos se dissecarmos mais esta sintaxe da lista de declaradores:

(6.7.6) declarator:
apontador opt direct-declarator
...
(6.7.6) ponteiro:
* type-qualifier-list opt
* tipo-qualifier-list opt ponteiro

Onde um declarador é a declaração inteira, um declarador direto é o identificador (nome da variável) e um ponteiro é a estrela seguida por uma lista de qualificadores de tipo opcional pertencente ao próprio ponteiro.

O que torna inconsistentes os vários argumentos de estilo sobre "a estrela pertence à variável" é que eles se esqueceram desses qualificadores de tipo de ponteiro. int* const x, int *const xOu int*const x?

Considere int *const a, b;, quais são os tipos de ae b? Não é tão óbvio que "a estrela pertence à variável" por mais tempo. Em vez disso, começaria a refletir sobre ondeconst pertence.

Você pode definitivamente argumentar que a estrela pertence ao qualificador de tipo de ponteiro, mas não muito além disso.

A lista de qualificadores de tipo para o ponteiro pode causar problemas para aqueles que usam o int *aestilo. Aqueles que usam ponteiros dentro de um typedef(o que não devemos, muito má prática!) E pensam que "a estrela pertence ao nome da variável" tendem a escrever esse bug muito sutil:

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

Isso compila de forma limpa. Agora você pode pensar que o código está correto. Não tão! Este código é acidentalmente uma falsificação de correção const.

O tipo de fooé realmente int*const*- o ponteiro mais externo foi feito somente leitura, não o apontado para os dados. Então, dentro desta função, podemos fazer **foo = n;e isso mudará o valor da variável no chamador.

Isso ocorre porque na expressão const bad_idea_t *foo, o *não pertence ao nome da variável aqui! No pseudo-código, esta declaração de parâmetro deve ser lida como const (bad_idea_t *) fooe não como (const bad_idea_t) *foo. A estrela pertence ao tipo de ponteiro oculto nesse caso - o tipo é um ponteiro e um ponteiro qualificado como const é escrito como *const.

Mas a raiz do problema no exemplo acima é a prática de ocultar os ponteiros atrás de um typedefe não o* estilo estilo.


Em relação à declaração de várias variáveis ​​em uma única linha

Declarar várias variáveis ​​em uma única linha é amplamente reconhecido como uma má prática 1) . O CERT-C resume muito bem como:

DCL04-C. Não declare mais de uma variável por declaração

Apenas lendo o inglês, o senso comum concorda que uma declaração deve ser uma declaração.

E não importa se as variáveis ​​são indicadores ou não. Declarar cada variável em uma única linha torna o código mais claro em quase todos os casos.

Portanto, a discussão sobre o programador ficar confuso int* a, bé ruim. A raiz do problema é o uso de vários declaradores, não a colocação do *. Independentemente do estilo, você deve escrever isso:

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

Outro argumento sólido, mas subjetivo, seria aquele dado int* ao tipo dea is sem dúvida int*, a estrela pertence ao qualificador de tipo.

Mas, basicamente, minha conclusão é que muitos dos argumentos postados aqui são apenas subjetivos e ingênuos. Você realmente não pode argumentar válido para nenhum dos dois estilos - é realmente uma questão de preferência pessoal subjetiva.


1) CERT-C DCL04-C .


De maneira divertida, se você ler o artigo que eu linkei, há uma pequena seção sobre o tópico typedef int *bad_idea_t; void func(const bad_idea_t bar); Como o grande profeta Dan Saks ensina "Se você sempre coloca o constmais à direita possível, sem alterar o significado semântico", isso cessa completamente ser um problema. Também torna suas constdeclarações mais consistentes de ler. "Tudo à direita da palavra const é aquilo que é const, tudo à esquerda é seu tipo". Isso se aplicará a todos os consts em uma declaração. Experimente-o comint const * * const x;
dgnuff

-1

Considerar

int *x = new int();

Isso não se traduz em

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

Na verdade, traduz para

int *x;
x = new int();

Isso torna a int *xnotação um tanto inconsistente.


newé um operador C ++. Sua pergunta está marcada com "C" e não "C ++".
Sapphire_Brick
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.