paulo1205
(usa Ubuntu)
Enviado em 14/12/2021 - 09:44h
Atribuir um rótulo a uma estrutura ou união é necessário para criar um tipo de dados novo e com uma identidade conhecida, a fim de que você possa declarar variáveis desse tipo e de tipos relacionados (ponteiros,
arrays etc.) em outras partes do programa, incluindo como parâmetros ou como tipo de retorno de funções.
Se você não atribuir um rótulo à sua estrutura ou união, ainda assim um novo tipo anônimo é criado, mas você não tem controle sobre ele, e não vai conseguir criar outros objetos do mesmo tipo em outras partes do programa. O tipo anônimo em si e todas as variáveis que você porventura declarar junto com ele só serão visíveis no escopo de sua declaração, quer seja um escopo amplo, como o escopo global, quer seja um escopo reduzido, como um bloco ou uma lista de parâmetros de função. Mesmo que você repita a declaração do tipo anônimo
ipsis litteris depois, a repetição será de um tipo anônimo distinto das declarações anteriores. Desse modo, o código abaixo vai produzir erros quando você tentar compilá-lo.
// arquivo “b.c”.
struct { int i; char c; } s1; // Uma estrutura anônima.
struct { int i; char c; } s2; // Outra estrutura anônima diferente embora com a mesma forma da primeira.
void f(void){ s1=s2; } // ERRO: tipo de s1 é incompatível com o de s2, ainda que tenham a mesma forma.
// Compilador vai reclamar que a estrutura abaixo não será visível em lugar nenhum do programa fora dos parênteses da declaração de parâmetros
// (nem mesmo dentro do corpo da função), o que, na prática, impede essa função de ser usada de modo devido por qualquer um que quisesse usá-la.
void g(const struct { int i; char c; } *p){
s1.i=p->i; // OK: atribui um campo inteiro a outro campo inteiro.
s2=*p; // ERRO: tipo de s2 é incompatível com tipo de *p, ainda que tenham a mesma forma.
}
void h(void){
struct { int i; char c; } l1={0, 0};
struct { int i; char c; } l2={0, 0};
g(&l1); // ERRO: o ponteiro &l1 é diferente do parâmetro de g(), ainda que tenham a mesma forma.
g(&l2); // ERRO: o ponteiro &l2 é diferente do parâmetro de g(), ainda que tenham a mesma forma.
g((void *)&s1); // TRAPAÇA contra o sistema de tipos do C (e não funciona em C++)! Não use!!!
} $ gcc -Wextra -Werror -O0 -pedantic-errors -c b.c
b.c: In function ‘f’:
b.c:6:17: error: incompatible types when assigning to type ‘struct <anonymous>’ from type ‘struct <anonymous>’
void f(void){ s1=s2; } // ERRO: tipo de s1 é incompatível com o de s2, ainda que tenham a mesma forma.
^
b.c: At top level:
b.c:10:14: error: anonymous struct declared inside parameter list will not be visible outside of this definition or declaration [-Werror]
void g(const struct { int i; char c; } *p){
^~~~~~
b.c: In function ‘g’:
b.c:12:4: error: incompatible types when assigning to type ‘struct <anonymous>’ from type ‘const struct <anonymous>’
s2=*p; // ERRO: tipo de s2 é incompatível com tipo de *p, ainda que tenham a mesma forma.
^
b.c: In function ‘h’:
b.c:18:4: error: passing argument 1 of ‘g’ from incompatible pointer type [-Wincompatible-pointer-types]
g(&l1); // ERRO: o ponteiro &l1 é diferente do parâmetro de g(), ainda que tenham a mesma forma.
^
b.c:10:6: note: expected ‘const struct <anonymous> *’ but argument is of type ‘struct <anonymous> *’
void g(const struct { int i; char c; } *p){
^
b.c:19:4: error: passing argument 1 of ‘g’ from incompatible pointer type [-Wincompatible-pointer-types]
g(&l2); // ERRO: o ponteiro &l2 é diferente do parâmetro de g(), ainda que tenham a mesma forma.
^
b.c:10:6: note: expected ‘const struct <anonymous> *’ but argument is of type ‘struct <anonymous> *’
void g(const struct { int i; char c; } *p){
^
cc1: all warnings being treated as errors
Normalmente você atribui rótulos às estruturas que você definir, o que tem tanto o propósito de permitir a reutilização do mesmo tipo de dados em outras partes do programa quanto o de melhorar os diagnósticos produzidos pelo compilador, sobretudo em caso de uso indevido ou tentativas de violação de tipo.
Alternativamente, você pode usar
typedef para atribuir um apelido fixo a um tipo específico de dados, incluindo estruturas e uniões anônimas.
O programa acima pode ser reescrito usando rótulos e apelidos. Deixei ainda alguns erros de tipos incompatíveis só para você comparar mensagens de erro, e ver como ficam mais claras.
// arquivo “c.c”.
struct S1 { int i; char c; } s1; // Estrutura com um nome.
typedef struct { int i; char c; } T2; // Estrutura anônima associada a um apelido específico por meio de typedef (pode-se usar o apelido).
T2 s2;
void f(void){ s1=s2; } // ERRO: tipo de s1 (struct S1) é incompatível com o de s2 (T2), mas agora ambos os tipos podem ser bem determinados (não é mais “anônimo incompatível com anônimo”).
// Como o tipo de p é conhecido, o compilador pode verificar se o tipo é compatível e exibir mensagens de diagnótico relevantes.
void g(const T2 *p){
s2.i=p->i; // OK: atribui um campo inteiro a outro campo inteiro.
s1=*p; // ERRO: tipo de s1 (struct S1) é incompatível com tipo de *p (T2).
s2=*p; // OK: mesmo tipo.
}
void h(void){
struct S1 l1={0, 0};
T2 l2={0, 0};
g(&l1); // ERRO: o ponteiro &l1 (struct S1 *) é diferente do que a função espera (const T2 *).
g(&l2); // OK: mesmo tipo.
g((void *)&l1); // TRAPAÇA contra o sistema de tipos do C (e não funciona em C++)! Não use!!!
} $ gcc -Wextra -Werror -O0 -pedantic-errors -c c.c
c.c: In function ‘f’:
c.c:7:17: error: incompatible types when assigning to type ‘struct S1’ from type ‘T2 {aka struct <anonymous>}’
void f(void){ s1=s2; } // ERRO: tipo de s1 (struct S1) é incompatível com o de s2 (T2), mas agora ambos os tipos podem ser bem determinados (não é mais “anônimo incompatível com anônimo”).
^
c.c: In function ‘g’:
c.c:12:4: error: incompatible types when assigning to type ‘struct S1’ from type ‘T2 {aka const struct <anonymous>}’
s1=*p; // ERRO: tipo de s1 (struct S1) é incompatível com tipo de *p (T2).
^
c.c: In function ‘h’:
c.c:19:4: error: passing argument 1 of ‘g’ from incompatible pointer type [-Wincompatible-pointer-types]
g(&l1); // ERRO: o ponteiro &l1 (struct S1 *) é diferente do que a função espera (const T2 *).
^
c.c:10:6: note: expected ‘const T2 * {aka const struct <anonymous> *}’ but argument is of type ‘struct S1 *’
void g(const T2 *p){
^
Quando usar rótulos e quando usar apelidos? Um caso em que o rótulo é necessário é quando você tem uma estrutura com um elemento que aponta para um dado do mesmo tipo que ele, tal como numa lista encadeada, que uma forma semelhante ao que vai abaixo.
struct list_node {
/* Um ou mais campos de dados. */
struct list_node *previous, *next; // ponteiros para os nós vizinhos.
};
Fora isso, uma orientação básica (que eu nunca vi escrita, mas que parece ser uma prática generalizada) é:
• quando o usuário precisar saber que se trata de uma estrutura ou união, por ter de lidar diretamente com seus campos internos, então deve-se usar “struct
rótulo ” e “union
rótulo ” (exemplos de usos assim em bibliotecas de uso comum: a
struct tm de <time.h> na biblioteca padrão do C, a
struct timeval de <sys/time.h> ou <sys/select.h> em sistemas POSIX, e a
union REGS do <dos.h>, em antigos compiladores para MS-DOS);
• caso contrário (i.e. quando o usuário não precisar ou não dever manipular campos internos da estrutura), geralmente se usa um apelido para o tipo, que esconde se se trata de uma estrutura, união, ou mesmo
array , e o usuário quase sempre trabalha apenas com ponteiros para o tipo (exemplos de uso incluem o
FILE de <stdio.h> na biblioteca padrão, o
regex_t de <regex.h> em sistemas POSIX, e o
DB de <db.h> da biblioteca Berkeley DB).
Na impede que uma estrutura ou união tenha tanto um rótulo quanto um apelido, mas isso é pouco comum no primeiro dos casos acima, que quase nunca têm apelidos; mais comum é que os tipos opacos, designados principalmente por seus apelidos, possuam rótulos de uso interno e nomes reservados (i.e. que começam com um ou dois caracteres de sublinhado (“_”)), não divulgados para os usuários finais.
Em C++, rótulos podem ser aplicados a classes, estruturas, uniões e enumerações, e seu uso é tão incentivado que o rótulo pode automaticamente ser usado para designar um tipo de dados, sem precisar repetir a palavra-chave
class ,
struct ,
union ou
enum , quase como se fossem apelidos atribuídos automaticamente.
#include <ctime> // Versão C++ do <time.h> do C.
struct tm ts1; // OK: declaração no estilo do C.
tm ts2; // OK: Declaração no estilo do C++, sem precisar repetir a palavra-chave “struct”. ts1 e ts2 têm o mesmo tipo.
class C { /* bla, bla, bla * };
C c1; // Eu poderia também dizer “class C c1;”, mas para quê digitar seis caracteres a mais?
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)