Uma coisa que sempre me pareceu intuitivamente um recurso positivo do C (bem, na verdade, de suas implementações como gcc, clang, ...) é o fato de ele não armazenar nenhuma informação oculta ao lado de suas próprias variáveis em tempo de execução. Com isso, quero dizer que, por exemplo, se você quiser uma variável "x" do tipo "uint16_t", pode ter certeza de que "x" ocupará apenas 2 bytes de espaço (e não carregará nenhuma informação oculta como seu tipo, etc. .). Da mesma forma, se você deseja uma matriz de 100 números inteiros, pode ter certeza de que é tão grande quanto 100 números inteiros.
No entanto, quanto mais eu estou tentando criar casos de uso concretos para esse recurso, mais me pergunto se ele realmente tem alguma vantagem prática. A única coisa que eu consegui chegar até agora é que ele obviamente precisa de menos RAM. Para ambientes limitados, como chips AVR etc., essa é definitivamente uma grande vantagem, mas para os casos de uso de desktop / servidor todos os dias, isso parece ser irrelevante. Outra possibilidade em que estou pensando é que ela pode ser útil / crucial para acessar o hardware ou talvez mapear regiões de memória (por exemplo, para saída VGA e similares) ...?
Minha pergunta: existem domínios concretos que não podem ou podem ser implementados com muita dificuldade sem esse recurso?
PS Por favor, diga-me se você tem um nome melhor para ele! ;)
fonte
virtual
função de membro. Portanto, o RTTI nunca aumenta o tamanho de nenhum objeto, apenas aumenta o binário por uma constante.T *
sempre tem o mesmo tamanho eT
pode conter um campo oculto que aponta para a tabela. E nenhum compilador C ++ inseriu vtables em objetos que não precisam deles.Respostas:
Existem vários benefícios, o óbvio é o tempo de compilação para garantir que itens como parâmetros de função correspondam aos valores passados.
Mas acho que você está perguntando sobre o que está acontecendo em tempo de execução.
Lembre-se de que o compilador criará um tempo de execução que incorpora o conhecimento dos tipos de dados nas operações que ele executa. Cada parte dos dados na memória pode não ser autoexplicativa, mas o código sabe inerentemente o que são esses dados (se você fez seu trabalho corretamente).
Em tempo de execução, as coisas são um pouco diferentes do que você pensa.
Por exemplo, não assuma que apenas dois bytes são usados quando você declara uint16_t. Dependendo do processador e do alinhamento de palavras, ele pode ocupar 16, 32 ou 64 bits na pilha. Você pode achar que sua variedade de shorts consome muito mais memória do que o esperado.
Isso pode ser problemático em determinadas situações em que você precisa fazer referência a dados em compensações específicas. Isso acontece ao se comunicar entre dois sistemas que possuem arquiteturas de processador diferentes, por meio de um link sem fio ou por arquivos.
C permite especificar estruturas com granularidade no nível de bit:
Essa estrutura tem três bytes de comprimento, com um short definido para iniciar em um deslocamento ímpar. Ele também precisará ser compactado para ser exatamente como você o definiu. Caso contrário, o compilador alinhará os membros por palavra.
O compilador irá gerar código nos bastidores para extrair esses dados e copiá-los em um registro para que você possa fazer coisas úteis com ele.
Agora você pode ver que toda vez que meu programa acessar um membro da estrutura myMessage, ele saberá exatamente como extraí-lo e operá-lo.
Isso pode se tornar problemático e difícil de gerenciar ao se comunicar entre diferentes sistemas com diferentes versões de software. Você deve projetar cuidadosamente o sistema e o código para garantir que ambos os lados tenham exatamente a mesma definição dos tipos de dados. Isso pode ser bastante desafiador em alguns ambientes. É aqui que você precisa de um protocolo melhor que contenha dados autoexplicativos, como os Buffers de Protocolo do Google .
Por fim, você faz questão de perguntar o quanto isso é importante no ambiente de desktop / servidor. Realmente depende da quantidade de memória que você planeja usar. Se você estiver executando algo como processamento de imagem, poderá acabar usando uma grande quantidade de memória que pode afetar o desempenho do seu aplicativo. Definitivamente, isso sempre é uma preocupação no ambiente incorporado, onde a memória é restrita e não há memória virtual.
fonte
short
. Mas este é um requisito único para o início da matriz, o restante é automaticamente alinhado corretamente por ser consecutivo.uint8_t padding: 6;
, assim como os dois primeiros bits. Ou, mais claramente, apenas o comentário//6 bits of padding inserted by the compiler
. A estrutura, como você a escreveu, tem um tamanho de pelo menos nove bytes, não três.Você encontrou um dos únicos motivos pelos quais isso é útil: mapear estruturas de dados externas. Isso inclui buffers de vídeo mapeados na memória, registros de hardware, etc. Eles também incluem dados transmitidos intactos fora do programa, como certificados SSL, pacotes IP, imagens JPEG e praticamente qualquer outra estrutura de dados que tenha uma vida persistente fora do programa.
fonte
C é uma linguagem de baixo nível, quase um montador portátil, portanto suas estruturas de dados e construções de linguagem estão próximas do metal (as estruturas de dados não têm custos ocultos - exceto restrições de preenchimento, alinhamento e tamanho impostas pelo hardware e pela ABI ). Portanto, C realmente não possui digitação dinâmica nativamente. Mas se você precisar, você pode adotar uma convenção de que todos os seus valores são agregados, começando com algumas informações de tipo (por exemplo, algumas
enum
...); utilizaçãounion
-s e (para a matriz como as coisas) membro da matriz flexível emstruct
contendo também o tamanho da matriz.(ao programar em C, é de sua responsabilidade definir, documentar e seguir convenções úteis - principalmente pré e pós-condições e invariantes; também a alocação dinâmica de memória C requer convenções
free
explicativas sobre quem deve algumamalloc
zona de memória acumulada)Portanto, para representar valores que são inteiros ou cadeias de caracteres em caixa, ou algum tipo de símbolo semelhante ao esquema ou vetores de valores, você usará conceitualmente uma união marcada (implementada como uma união de ponteiros) sempre começando pelo tipo de tipo -, por exemplo:
Para obter o tipo dinâmico de algum valor
Aqui está um "elenco dinâmico" para vetores:
e um "acessador seguro" dentro de vetores:
Você geralmente define a maioria das funções curtas acima, como
static inline
em algum arquivo de cabeçalho.BTW, se você pode usar o coletor de lixo da Boehm, poderá codificar com bastante facilidade em um estilo de nível superior (mas não seguro), e vários intérpretes do Scheme são feitos dessa maneira. Um construtor de vetor variável pode ser
e se você tiver três variáveis
você pode construir um vetor deles usando
make_vector(3,v1,v2,v3)
Se você não quiser usar o coletor de lixo de Boehm (ou criar o seu próprio), tenha muito cuidado ao definir destruidores e documentar quem, como e quando a memória deve ser
free
-d; veja este exemplo. Então você pode usarmalloc
(mas testar contra a falha) em vez deGC_MALLOC
acima, mas precisa definir e usar com cuidado alguma função destruidoravoid destroy_value(value_t)
A força de C é ter um nível baixo o suficiente para tornar possível o código acima e definir suas próprias convenções (específicas ao seu software).
fonte