Como o armazenamento const funciona? (Item 2, Scott Myers Effective C ++)

8

No Item2 na página 16, (Preferir consts, enumerações e linhas para #defines), Scott diz:

Além disso, embora bons compiladores não reservem armazenamento para objetos const de tipos inteiros ...

Eu não entendo isso. Se eu definir um objeto const, por exemplo,

const int myval = 5;

então certamente o compilador deve reservar um pouco de memória (de tamanho int) para armazenar o valor 5?

Ou os dados const são armazenados de alguma maneira especial?

Esta é mais uma questão de armazenamento do computador, suponho. Basicamente, como o computador armazena objetos const para que nenhum armazenamento seja reservado?

user619818
fonte
3
Você deve fornecer um título claro. por exemplo, storage of const objecta fonte da sua pergunta tem pouco valor.
Simon Bergot

Respostas:

7

Além disso, embora bons compiladores não reservem armazenamento para objetos const de tipos inteiros ...

Uma afirmação um pouco mais correta seria que os compiladores não reservariam memória de dados para objetos const do tipo inteiro: eles trocariam por memória de programa . Não há diferença entre os dois na arquitetura Von Neumann, mas em outras arquiteturas, como Harvard, a distinção é bastante importante.

Para entender completamente o que está acontecendo, é preciso lembrar como a linguagem assembly carrega dados para processamento. Existem duas maneiras fundamentais de carregar os dados - ler uma memória de um local específico (o chamado modo de endereçamento direto ) ou definir uma constante especificada como parte da própria instrução (o chamado modo de endereçamento imediato ). Quando o compilador vê uma const int x = 5declaração seguida por int a = x+x, ele tem duas opções:

  • Coloque 5 na memória de dados, como se fosse uma variável, e gere instruções de carregamento direto; tratar gravações em x como erros
  • Sempre que xfor referenciado, gere uma instrução de carga imediata do valor 5

No primeiro caso, você verá uma leitura xno registro do acumulador , uma adição do valor no local do xacumulador e uma loja no local de a. No segundo caso, você verá uma carga imediata de cinco, uma adição imediata de cinco, seguida por uma loja no local de a. Alguns compiladores pode descobrir que você está adicionando uma constante para si, otimizar a = x+xpara a = 10, e gerar uma única instrução que armazena dez no local a.

dasblinkenlight
fonte
1
+1 por mencionar também fold constante
jk.
10

Não necessariamente. Também pode optar por usar apenas o valor bruto 5 em vez de myvalno código compilado.

A diferença entre #define MYVAL 5e const int myval = 5é que, no primeiro caso, o compilador não tem escolha alguma, pois o pré-processador já substituiu todas as menções MYVALno código-fonte 5no momento em que o compilador vê o código-fonte. No último caso, porém, há uma escolha. Para uma compilação de depuração não otimizada, o compilador pode alocar explicitamente a const int, portanto, no depurador, você poderá ver a constante myval, em vez de apenas o valor bruto 5.

Péter Török
fonte
Eu entendo o benefício da const ao invés de definir. Mas mesmo com o exemplo const, o valor 5 deve ser armazenado no executável em algum lugar?
user619818
3
@ user619818, será armazenado dentro do código como um parâmetro de instrução, não no segmento de dados como uma variável / constante regular.
Péter Török
@ user619818: A instrução sempre terá um parâmetro. Portanto, se o armazenamento separado for alocado, há o valor e o endereço dele no argumento imediato da instrução. Em CPUs com tamanho de instrução fixo (como ARM), a situação fica ainda pior, porque pequenas constantes como 5 se encaixam na própria instrução de 32 bits, mas o endereço geralmente não e causa a emissão de instruções extras de endereço de carga.
Jan Hudec
3

O que a citação diz não é muito correto.

Um bom compilador não reservará armazenamento para variáveis ​​const estáticas . Se a variável const não é estática e está no escopo do arquivo, ela deve reservar armazenamento, porque a variável pode ser referenciada a partir de outra unidade de compilação. Com as otimizações de tempo do link, o vinculador poderá eliminar o armazenamento e reescrever instruções que fazem referência à variável, se puder provar que o programa não gera um ponteiro para essa variável.

Uma razão muito melhor para usar, em const intvez de, #defineé que os depuradores não "veem" as macros; portanto, você não pode inspecionar um #definedvalor no depurador.

zvrba
fonte
3

Roubarei a primeira frase da resposta de Péter Török, mas elaborarei de maneira diferente: não necessariamente. Também pode optar por usar apenas o valor bruto 5 em vez de myvalno código compilado.

Tratar myvalcomo uma variável regular alocando espaço para ela na memória pode ter implicações de desempenho que variam de minúsculas a severas, dependendo da arquitetura e de como ela lida com a memória.

Trabalhando dessa maneira, um compilador emitirá uma instrução que diz algo como "carregar o registrador R com o que estiver no local da memória myval". A localização demyvalcomo um operando da instrução, ela sai diretamente do mesmo bloco de dados que a própria instrução. Em CPUs modernas, esse valor estará prontamente disponível no chip devido à pré-busca de instruções. Com o endereço em mãos, a CPU ainda precisa obter o valor da memória. Isso pode ocorrer rapidamente se o local estiver próximo no cache ou não tão rapidamente se não estiver. Não apenas a CPU precisa sair do chip para obter o valor, como pode forçá-la a extrair outros dados mais úteis do cache que precisarão ser trazidos mais tarde. Quando o programa está sendo executado em um sistema operacional que virtualiza a memória, o acesso a esse local pode causar uma falha na página, resultando na suspensão do programa até que a página necessária seja trazida para a RAM via E / S periférica (por exemplo, disco),

Ao conectar o valor constante ao código do objeto, o compilador emitirá uma instrução como "load register R with the value 5". Como o endereço de memória descrito acima, 5seria um operando para a instrução e disponível da mesma maneira (ou seja, pré-buscado). É aí que a semelhança termina, porque a CPU agora tem tudo o que precisa para colocar o 5registro R e continuar com seus negócios. Como endereços e registradores geralmente têm o mesmo tamanho, não há diferença no número de bytes que a instrução ocupa e a execução real ocorre com zero chances de erros de cache e falhas de página que podem ocorrer quando você retira algo da memória.

O compilador pode, como Péter apontou, alocar espaço e um símbolo para myvalcompilações de depuração. Não haveria nenhum mal em fazer isso e ainda manter a fiação do seu valor, já que o valor permanece o mesmo, não importa o quê e o símbolo esteja realmente lá para nós, humanos, usarmos na depuração.

Observe que isso se aplica apenas a valores que podem ser mantidos em registros, porque os registros são números inteiros por natureza. Outras constantes acabarão na memória.

Blrfl
fonte
1

O compilador substituirá o número cinco sempre que a variável 'myval' for usada.

Dibbeke
fonte
0

O compilador pode considerar valores const como operandos imediatos. Operandos imediatos não requerem armazenamento de dados. O compilador pode tratar:

int foo = myVal;

o mesmo que

int foo = 5;

O valor 5 não é armazenado na memória de dados, mas como parte da sequência de instruções.

O compilador deve reservar armazenamento de dados nos casos em que o endereço de um valor possa ser obtido. Mesmo nesse caso, o compilador ainda usará operações imediatas quando o valor de myVal for usado.

Bill Door
fonte