paulo1205
(usa Ubuntu)
Enviado em 12/03/2019 - 12:04h
Vai depender do caso. É como um problema de engenharia, em que cada técnica tem suas vantagens e desvantagens, e a melhor solução para um caso pode não se aplicar tão bem a outro.
Você está falando de um arquivo .h, que possa ser incluído por múltiplos arquivos .c, ou de um único arquivo .c?
Se for num .h que possa ser incluído por múltiplos arquivos, então as soluções que você mostrou usando variáveis não-estáticas estão todas erradas (mas continue lendo para entender o caso das estáticas). Melhor seria fazer algo como o que vai abaixo.
// arquivo.h
extern const int valor;
extern const char nome[6]; // Tenho de saber o tamanho
extern const char *const outro_nome; // Se eu não souber o tamanho, tenho de usar ponteiro. // arquivo.c, que implementa declarações de arquivo.h
#include "arquivo.h"
const int valor=1000;
const char nome[6]="Paulo";
const char *const outro_nome="Anaxágoras";
Outro aspecto que você tem de considerar num arquivo .h é se ele vai ser usado apenas por programas em C, apenas por programas em C++ ou por ambos, pois existem algumas diferenças semânticas em declarações de variáveis e no comportamento de constantes entre C e C++.
Em C++ puro seria aceitável que o conteúdo mostrado acima em arquivo.c tivesse sido colocado diretamente em um arquivo.h, mas essas variáveis teriam o mesmo sentido que teriam em C se tivessem sido declaradas com o modificador
static : elas não seriam realmente globais, mas teriam, isto sim, escopo de arquivo na unidade de tradução usada na compilação. Isso significa que se você tiver um programa composto por múltiplos arquivos .c e vários deles incluírem o mesmo arquivo .h que declara as variáveis constantes, você terá várias versões dessas variáveis, com cada arquivo .c que compõe o programa enxergando sua própria versão. No caso de constantes, isso não vai causar erro de execução (todas as constantes serão iguais), mas pode provocar desperdício . Se você quiser ter uma versão apenas de cada constante, tem de usar a declaração com
extern no .h e a implementação com definição de valores num .c.
Outro fator que influencia a escolha, e que provoca comportamentos distintos em C e em C++, é o uso que você pretende fazer dessas constantes. Veja, por exemplo, o seguinte código.
const int MAX=100;
char global_buffer[MAX];
void func(void){
static char local_static_buffer[MAX];
char local_buffer[MAX];
/* ... */
}
Esse código é perfeitamente válido em C++, produzindo um
array com alocação estática e com escopo de arquivo para
global_buffer e, dentro de
func (), com escopo de bloco, um
array com alocação estática para
local_static_buffer e um
array com alocação automática para
local_buffer . Em C, as declarações de
global_buffer e
local_static_buffer dariam erro, e a de
local_buffer seria válida, mas não produziria um
array de alocação automática, mas sim um
array com alocação dinâmica. Isso acontece porque em C++ variaveis declaradas como constantes podem ter seu valor constante substituído no restante do código em C++ durante a compilação, ao passo que, em C, o valor constante só é usado no momento da execução; durante a compilação, o compilador entende que aquele símbolo
MAX é uma variável como qualquer outra, logo todas as declarações de
arrays tendo
MAX como tamanho designam VLAs (
variable-length arrays , ou
arrays de tamanho variável), que são incompatíveis com declarações estáticas, uma vez que têm de ser manipulados dinamicamente.
Assim sendo, se você quiser que a constante que você definir possa ser usada para calcular limites de tamanho para
arrays estáticos ou automáticos em C, não pode defini-la como variável constante, mas terá de usar ou uma macro constante (com
#define ) ou uma constante de tipo enumerado (com
enum ).
Entre macros e constantes de enumeração há também alguns
trade-offs . Você tem de pesar as vantagens e desvantagens de cada um.
Macros têm como vantagens os seguintes pontos:
• permitem definir constantes de vários tipos (inteiras, de ponto-flutuante, ponteiros e contantes literais de
strings );
• no caso de constantes literais de
strings , elas podem ser justapostas a outras
strings para formar uma única
string composta;
• flexibilidade: por ser definida e poder ser testada pelo preprocessador, antes de o próprio compilador começar a traduzir código em C para código de máquina, de modo a permitir inclusive compilaço condicional de partes de código, em função da existência da definição de macros ou dos valores a elas atribuídos;
• permitem que o programador predefina a macro com um valor potencialmente diferente, como no seguinte exemplo;
// arquivo.h, com exemplo inspirado em caso real da biblioteca de sockets e <sys/select.h>
#ifndef FD_SETSIZE
#define FD_SETSIZE 256
#endif
typedef struct fd_set_t { int fd_bits[FD_SETSIZE]; } fd_set; // Exemplo de uso predefinindo o valor da macro com algo maior (note #include após #define).
#define FD_SETSIZE 16384
#include "arquivo.h" • é o mecanismo para definição de constante mais comum entre os programadores de C, de modo que novas macros serão geralmente bem recebidas por gente do meio (mas enfrenta resistência um pouco maior em C++).
Como desvantagens de macros, tem-se, entre outras:
• insegurança: é possível indefinir uma macro previamente definida por meio da diretiva
#undef do preprocessador;
• insegurança: o valor da macro pode ser redefinido ao longo do programa, inclusive com novos valores incompatíveis com o o tipo e com o propósito do valor original;
• o pouco acoplamento ou baixa integração entre o preprocessador e o compilador dão margem ao aparecimento de conflitos (por exemplo, uma macro que, por acidente ou desconhecimento, calha de ter o mesmo nome de algum símbolo do programa) e situações difíceis de depurar, porque a macro não é um símbolo visível para o compilador, mas substitui seu valor antes de a compilação efetivamente começar;
• uma macro só tem sua sintaxe examinada quando ela é usada; então, se a macro for definida com algum erro de sintaxe mas permanecer muito tempo sem uso, esse erro vai ficar sem ser reportado;
• compilação condicional estimula o uso de soluções de baixa portabilidade, e pode vir a se tornar um problema para a manutenção de código.
Constantes de enumerações, por sua vez, têm as seguintes características:
• são símbolos no contexto da compilação, de modo que não correm o risco de causar conflitos com outros símbolos sem que o compilador consiga diagnosticar com clareza;
• não é possível esquecer ou sobrescrever um valor atribuído à constante de enumeração;
• só pode ser usada com valores constantes que sejam inteiros (nada de ponteiros, nem valores de ponto flutuante ou
strings );
• se compartilhada com código em C++, alguns contextos de uso podem gerar comportamentos inesperados, porque o tipo enumerado é um tipo distinto de
int e a conversão automática para inteiro nem sempre acontece (isso é particularmente verdadeiro no caso de
templates );
• se compartilhada com código em C++, o tipo de inteiro usado internamente para implementar a enumeração pode ser diferente do tipo usado em C (pouco provável na prática, mas teoricamente possível);
• não é muito grande a adesão ao uso de constantes de enumeração entre os programadores em C e na bibliotecas mais comuns; o uso de macros ainda é mais prevalente (creio que, além de questões culturais, os fatores acima contribuem para a baixa adesão).
... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)