Dúvidas com #define, const globais. [RESOLVIDO]

1. Dúvidas com #define, const globais. [RESOLVIDO]

Nick Us
Nick-us

(usa Slackware)

Enviado em 11/03/2019 - 04:23h

Deve ser uma dúvida básica, mas fiquei confuso novamente. Em uma necessidade de declarar uma variável para um arquivo, descobri que não sei qual o melhor meio de fazer isso! Vi todas essas formas, e deu um nó aqui para saber qual a melhor para usar! Eu havia me empolgado com o #define, mas nem sei se ele é o ideal para essas coisas!
O que eu realmente devo usar para isso. Lembrando o Nome nunca será mudado dentro do código, ou seja é constante!
#define MY_FILE "/mnt/dados/file.txt"

char My_File1[] = "/mnt/dados/file.txt";
const char My_File2[] = "/mnt/dados/file.txt";
static const char My_File3[] = "/mnt/dados/file.txt";

char *My_File4 = "/mnt/dados/file.txt";
const char *My_File5 = "/mnt/dados/file.txt";
static const char *My_File6 = "/mnt/dados/file.txt";

#define MAX 100

enum {
MAX2 = 100
};



  


2. MELHOR RESPOSTA

Paulo
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)

3. Re: Dúvidas com #define, const globais. [RESOLVIDO]

Nick Us
Nick-us

(usa Slackware)

Enviado em 12/03/2019 - 18:34h

paulo1205 escreveu: Vai depender do caso.

Compreendi o seu ponto. Mas ainda não estou fazendo testes com bibliotecas assim nesse nível.
No caso da pergunta o que estou tentando fazer é apenas criar uma variável que não precisa ser mudada, para ser usada no próprio código, que tem apenas um arquivo exemplo: test.c

Este algoritmo a única coisa que faz é lidar com 1 arquivo externo. Que estou declarando como variável.
E a outra situação também seria para número fixo.

Algumas situações eu percebi a necessidade do #define para números pq o compilador não aceita declarar uma variável para alguns casos, principalmente qdo esse valor são tamanhos de char exemplo char texto[TAMANHO]; ou dentro de struct. mas declarando no #define, ele aceita de boa.

O caso do nome e caminho de um arquivo, ficou a dúvida pq qualquer forma que eu declare funcione. O que não sei é se a forma como eu declaro é mais pesada é mais lenta usa mais recursos ou não, Pq a única coisa que quero é que o programa saiba onde o arquivo está. Apenas isso. E não quero escrever o nome do arquivo repetidas vezes, principalmente qdo ele muda o local original. Motivo que declaro ele na variável, mas ele é FIXO dentro do programa.


4. Re: Dúvidas com #define, const globais. [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 13/03/2019 - 04:26h

Nick-us escreveu:

Compreendi o seu ponto. Mas ainda não estou fazendo testes com bibliotecas assim nesse nível.


Minha resposta não foi apenas sobre arquivos de cabeçalhos, mas eu gastei um tempo falando sobre eles para explicar contextos de uso válidos para variáveis constantes. Mesmo que você não os use hoje, já vai saber evitar possíveis problemas no futuro.

No caso da pergunta o que estou tentando fazer é apenas criar uma variável que não precisa ser mudada, para ser usada no próprio código, que tem apenas um arquivo exemplo: test.c

Este algoritmo a única coisa que faz é lidar com 1 arquivo externo. Que estou declarando como variável.
E a outra situação também seria para número fixo.

Algumas situações eu percebi a necessidade do #define para números pq o compilador não aceita declarar uma variável para alguns casos, principalmente qdo esse valor são tamanhos de char exemplo char texto[TAMANHO]; ou dentro de struct. mas declarando no #define, ele aceita de boa.


Cuidado com a terminologia. Os tokens que você define com #define não são variáveis, mas macros, e eles são definidos, não declarados.

O caso do nome e caminho de um arquivo, ficou a dúvida pq qualquer forma que eu declare funcione. O que não sei é se a forma como eu declaro é mais pesada é mais lenta usa mais recursos ou não, Pq a única coisa que quero é que o programa saiba onde o arquivo está. Apenas isso. E não quero escrever o nome do arquivo repetidas vezes, principalmente qdo ele muda o local original. Motivo que declaro ele na variável, mas ele é FIXO dentro do programa.


O que eu disse na mensagem anterior continua valendo, especialmente a colocação de que as diversas alternativas lhe colocam um problema de engenharia, em que todas funcionam, mas cada uma tem vantagens ou desvantagens. Só que, como sua mensagem original soou para mim como se você estivesse mais preocupado com a forma básica, entre variável, macro ou constante de enumeração, e não com especificidades de cada uma das declarações que você usou.

Continua, portanto, sendo um problema de engenharia, em que você tem várias opções e a escolha entre elas pode se dar de acordo com vários critérios.

Um dos critérios pode ser a menor quantidade de digitação. Nesse caso, a macro MY_FILE e as declarações de My_File1 e My_File4 levam vantagem.

Outro critério pode ser o de comportamento correto na maior quantidade possível de casos de uso. Por esse critério, as piores opções são justamente My_File1 e My_File4, porque My_File1 simplesmente não indica um array constante e My_File4 é um ponteiro para dados mutáveis que, por mais uma dessas compatibilidades com código obsoleto e perigoso, pode apontar para um objeto que é de fato constante, de modo que se você, por acidente, disser algo como “My_File4[4]='\n'”, o compilador não vai reclamar, mas a execução vai dar erro (possivelmente SIGSEGV). My_File5 e My_File6 não ficam muito atrás porque, embora não permitam a modificação de elementos dos arrays para os quais apontam, eles podem passar a apontar para outros arrays, se você, por acidente, fizer algo como “My_File5=NULL” (em vez de “==”) ou “My_File6++” (se você mudasse o tipo deles para “const chat *const”, esses acidentes poderiam ser evitados). Ainda, se você quiser poder recuperar o tamanho dos arrays por meio do operador sizeof, para poder fazê-lo em tempo de compilação, em lugar de no momento da execução (com strlen(), por exemplo), tem mesmo de usar algo como My_File2 ou My_File3.

Se em qualquer momento você precisar de uma referência para o objeto constante, então macros e constantes de enumeração não poderão ser diretamente usadas. Esse pode ser mais um critério para você considerar.

E pode haver outros critérios. Se você os encontrar, continua valendo o mesmo princípio de ponderar vantagens e desvantagens de acordo com cada critério, e escolher o que for a melhor solução de compromisso entre todos esses critérios.

Quanto a ser o produto final da compilação mais ou menos pesado e mais ou menos eficiente, o único caso que me vem à mente é a possibilidade de usar sizeof em lugar de strlen(). Geralmente se diz que em C (e C++) você não paga pelo que não usa: devidamente configurados, ou o compilador vai lhe alertar sobre variáveis que, por estarem sem uso, podem ser suprimidas, ou o linker vai excluir do executável gerado aquilo que não for estritamente necessário. E, na hora de gerar o produto final não existe mágica: a macro que tiver sido usada vai ocupar tanto espaço em memória quanto uma variável constante (na verdade, frequentemente o compilador aloca espaço no mesmo local para ambas, e atribui um nome de visibilidade interna para acomodar o valor da macro e poder referir-se a ele depois, inclusive durante a fase de linking).

Quando eu faço essas escolhas em algum programa meu, eu costumo privilegiar critérios ligados à correção do código e melhor portabilidade e adequação a possíveis novos usos no futuro. Assim, mesmo que hoje o programa esteja todo num único arquivo .c, eu normalmente evito usar construções que dificultem uma eventual partição do programa em múltiplos arquivos.


... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)


5. Re: Dúvidas com #define, const globais. [RESOLVIDO]

Nick Us
Nick-us

(usa Slackware)

Enviado em 13/03/2019 - 10:21h

paulo1205 escreveu:Cuidado com a terminologia. Os tokens que você define com #define não são variáveis, mas macros, e eles são definidos, não declarados.

Muito boa essa explicação! Prova que tenho que estudar mais! Preciso saber mesmo essas diferenças!

E, na hora de gerar o produto final não existe mágica: a macro que tiver sido usada vai ocupar tanto espaço em memória quanto uma variável constante (na verdade, frequentemente o compilador aloca espaço no mesmo local para ambas

Essa resposta também me ajudou porque me dá outra direção de aprendizado, compreender melhor o uso dos recursos, eu já li e montei algoritmos que me informa o espaço ocupado, achei bem interessante, mas não dei continuidade porque não fiz nada ainda para poder otimizar. Penso eu que teria que ter um programa pronto para analisar o que ele consome. Vou chegar lá.

De qualquer forma, terei que estudar os 2 Textos! Muitas vezes! Obrigado!








Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts