Como os tipos de dados C são “suportados diretamente pela maioria dos computadores”?


114

Estou lendo “The C Programming Language” da K&R e me deparei com esta declaração [Introdução, p. 3]:

Como os tipos de dados e estruturas de controle fornecidos por C são suportados diretamente pela maioria dos computadores , a biblioteca de tempo de execução necessária para implementar programas independentes é pequena.

O que significa a declaração em negrito? Existe um exemplo de tipo de dados ou estrutura de controle que não é suportado diretamente por um computador?


1
Hoje em dia, a linguagem C suporta aritmética complexa, mas originalmente não suportava porque os computadores não suportam diretamente números complexos como tipos de dados.
Jonathan Leffler

12
Na verdade, era historicamente o contrário: C foi projetado a partir das operações e tipos de hardware disponíveis naquela época.
Basile Starynkevitch

2
A maioria dos computadores não tem suporte direto de hardware para flutuações decimais
PlasmaHH

3
@MSalters: Eu estava tentando dar uma dica em alguma direção para a pergunta "Existe um exemplo de tipo de dados ou estrutura de controle que não é suportado diretamente por um computador?" que eu não interpretei como limitado a K&R
PlasmaHH

11
Como isso não é uma duplicata mais de 6 anos após o lançamento do Stack Overflow?
Peter Mortensen

Respostas:


143

Sim, existem tipos de dados não suportados diretamente.

Em muitos sistemas embarcados, não há unidade de ponto flutuante de hardware. Então, quando você escreve um código como este:

float x = 1.0f, y = 2.0f;
return x + y;

Ele é traduzido em algo assim:

unsigned x = 0x3f800000, y = 0x40000000;
return _float_add(x, y);

Em seguida, o compilador ou a biblioteca padrão deve fornecer uma implementação de _float_add(), que ocupa memória em seu sistema embarcado. Se você está contando bytes em um sistema muito pequeno, isso pode soar.

Outro exemplo comum são os inteiros de 64 bits ( long longno padrão C desde 1999), que não são diretamente suportados por sistemas de 32 bits. Os sistemas SPARC antigos não suportavam multiplicação de inteiro, então a multiplicação tinha que ser fornecida pelo tempo de execução. Existem outros exemplos.

Outras línguas

Em comparação, outras linguagens têm primitivas mais complicadas.

Por exemplo, um símbolo Lisp requer muito suporte de tempo de execução, assim como tabelas em Lua, strings em Python, arrays em Fortran, etc. Os tipos equivalentes em C geralmente não fazem parte da biblioteca padrão (sem símbolos ou tabelas padrão) ou são muito mais simples e não requerem muito suporte de tempo de execução (arrays em C são basicamente apenas ponteiros, strings terminadas em nulo são quase tão simples).

Estruturas de controle

Uma estrutura de controle notável ausente em C é o tratamento de exceções. A saída não local é limitada a setjmp()e longjmp(), que apenas salva e restaura certas partes do estado do processador. Em comparação, o tempo de execução C ++ deve percorrer a pilha e chamar destruidores e manipuladores de exceção.


2
basicamente apenas indicadores ... em vez disso, basicamente apenas pedaços brutos de memória. Mesmo que isso seja minucioso e a resposta seja boa de qualquer maneira.
Deduplicator

2
Você pode argumentar que strings terminadas em nulo têm "suporte de hardware", já que o terminador de string se ajusta à operação 'salto se zero' da maioria dos processadores e, portanto, é ligeiramente mais rápido do que outras implementações possíveis de strings.
Peteris

1
Publiquei minha própria resposta para expandir como C é projetado para ser mapeado simplesmente para asm.
Peter Cordes

1
Por favor, não use a colocação "arrays são basicamente apenas ponteiros", isso pode enganar seriamente um iniciante como o OP. Algo parecido com "arrays são implementados diretamente usando ponteiros no nível de hardware" seria melhor IMO.
The Paramagnetic Croissant

1
@TheParamagneticCroissant: Acho que neste contexto é apropriado ... a clareza vem com o custo da precisão.
Dietrich Epp

37

Na verdade, aposto que o conteúdo desta introdução não mudou muito desde 1978, quando Kernighan e Ritchie os escreveram pela primeira vez na primeira edição do livro, e eles se referem à história e à evolução de C naquela época mais do que a moderna implementações.

Os computadores são fundamentalmente apenas bancos de memória e processadores centrais, e cada processador opera usando um código de máquina; parte do design de cada processador é uma arquitetura de conjunto de instruções, chamada de linguagem Assembly , que mapeia um a um de um conjunto de mnemônicos legíveis para o código de máquina, que é composto apenas por números.

Os autores da linguagem C - e das linguagens B e BCPL que a precederam imediatamente - tinham a intenção de definir construções na linguagem que fossem compiladas da maneira mais eficiente possível em Assembly ... na verdade, eles foram forçados a isso por limitações no alvo hardware. Como outras respostas apontaram, isso envolveu ramificações (GOTO e outros controles de fluxo em C), movimentos (atribuição), operações lógicas (& | ^), aritmética básica (adicionar, subtrair, aumentar, diminuir) e endereçamento de memória (ponteiros ) Um bom exemplo são os operadores pré / pós-incremento e decremento em C, que supostamente foram adicionados à linguagem B por Ken Thompson especificamente porque eram capazes de traduzir diretamente para um único opcode depois de compilados.

Isso é o que os autores querem dizer quando afirmam "suportado diretamente pela maioria dos computadores". Eles não queriam dizer que outras linguagens continham tipos e estruturas que não eram suportados diretamente - eles queriam dizer que, por design, construções C traduzidas mais diretamente (às vezes literalmente diretamente) em Assembly.

Essa relação próxima com o Assembly subjacente, embora ainda forneça todos os elementos necessários para a programação estruturada, é o que levou à adoção precoce do C e o que o mantém uma linguagem popular hoje em ambientes onde a eficiência do código compilado ainda é fundamental.

Para uma descrição interessante da história da linguagem, consulte The Development of the C Language - Dennis Ritchie


14

A resposta curta é, a maioria das construções de linguagem suportadas por C também são suportadas pelo microprocessador do computador de destino, portanto, o código C compilado traduz muito bem e eficiente para a linguagem assembly do microprocessador, resultando em código menor e uma pegada menor.

A resposta mais longa requer um pouco de conhecimento em linguagem assembly. Em C, uma declaração como esta:

int myInt = 10;

seria traduzido para algo assim em montagem:

myInt dw 1
mov myInt,10

Compare isso com algo como C ++:

MyClass myClass;
myClass.set_myInt(10);

O código de linguagem assembly resultante (dependendo de quão grande é MyClass ()), pode adicionar até centenas de linhas de linguagem assembly.

Sem realmente criar programas em linguagem assembly, C puro é provavelmente o código "mais fino" e "mais compacto" no qual você pode fazer um programa.

EDITAR

Dados os comentários à minha resposta, decidi fazer um teste, apenas para minha própria sanidade. Eu criei um programa chamado "test.c", que se parecia com isto:

#include <stdio.h>

void main()
{
    int myInt=10;

    printf("%d\n", myInt);
}

Compilei isso até o assembly usando o gcc. Usei a seguinte linha de comando para compilá-lo:

gcc -S -O2 test.c

Aqui está a linguagem assembly resultante:

    .file   "test.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "%d\n"
    .section    .text.unlikely,"ax",@progbits
.LCOLDB1:
    .section    .text.startup,"ax",@progbits
.LHOTB1:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB24:
    .cfi_startproc
    movl    $10, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    jmp __printf_chk
    .cfi_endproc
.LFE24:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE1:
    .section    .text.startup
.LHOTE1:
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

Em seguida, crio um arquivo chamado "test.cpp" que definiu uma classe e produziu a mesma coisa que "test.c":

#include <iostream>
using namespace std;

class MyClass {
    int myVar;
public:
    void set_myVar(int);
    int get_myVar(void);
};

void MyClass::set_myVar(int val)
{
    myVar = val;
}

int MyClass::get_myVar(void)
{
    return myVar;
}

int main()
{
    MyClass myClass;
    myClass.set_myVar(10);

    cout << myClass.get_myVar() << endl;

    return 0;
}

Compilei da mesma forma, usando este comando:

g++ -O2 -S test.cpp

Aqui está o arquivo de montagem resultante:

    .file   "test.cpp"
    .section    .text.unlikely,"ax",@progbits
    .align 2
.LCOLDB0:
    .text
.LHOTB0:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9set_myVarEi
    .type   _ZN7MyClass9set_myVarEi, @function
_ZN7MyClass9set_myVarEi:
.LFB1047:
    .cfi_startproc
    movl    %esi, (%rdi)
    ret
    .cfi_endproc
.LFE1047:
    .size   _ZN7MyClass9set_myVarEi, .-_ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE0:
    .text
.LHOTE0:
    .section    .text.unlikely
    .align 2
.LCOLDB1:
    .text
.LHOTB1:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9get_myVarEv
    .type   _ZN7MyClass9get_myVarEv, @function
_ZN7MyClass9get_myVarEv:
.LFB1048:
    .cfi_startproc
    movl    (%rdi), %eax
    ret
    .cfi_endproc
.LFE1048:
    .size   _ZN7MyClass9get_myVarEv, .-_ZN7MyClass9get_myVarEv
    .section    .text.unlikely
.LCOLDE1:
    .text
.LHOTE1:
    .section    .text.unlikely
.LCOLDB2:
    .section    .text.startup,"ax",@progbits
.LHOTB2:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB1049:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $10, %esi
    movl    $_ZSt4cout, %edi
    call    _ZNSolsEi
    movq    %rax, %rdi
    call    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
    xorl    %eax, %eax
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE1049:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE2:
    .section    .text.startup
.LHOTE2:
    .section    .text.unlikely
.LCOLDB3:
    .section    .text.startup
.LHOTB3:
    .p2align 4,,15
    .type   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, @function
_GLOBAL__sub_I__ZN7MyClass9set_myVarEi:
.LFB1056:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZStL8__ioinit, %edi
    call    _ZNSt8ios_base4InitC1Ev
    movl    $__dso_handle, %edx
    movl    $_ZStL8__ioinit, %esi
    movl    $_ZNSt8ios_base4InitD1Ev, %edi
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    jmp __cxa_atexit
    .cfi_endproc
.LFE1056:
    .size   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, .-_GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE3:
    .section    .text.startup
.LHOTE3:
    .section    .init_array,"aw"
    .align 8
    .quad   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .hidden __dso_handle
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

Como você pode ver claramente, o arquivo de montagem resultante é muito maior no arquivo C ++ do que no arquivo C. Mesmo se você cortar todas as outras coisas e apenas comparar o C "principal" com o C ++ "principal", há um monte de coisas extras.


14
Esse "código C ++" simplesmente não é C ++. E o código real, como MyClass myClass { 10 }em C ++, é muito provável que seja compilado exatamente no mesmo assembly. Os compiladores C ++ modernos eliminaram a penalidade de abstração. E, como resultado, muitas vezes podem vencer os compiladores C. Por exemplo, a penalidade de abstração em C qsorté real, mas C ++ std::sortnão tem penalidade de abstração mesmo após a otimização básica.
MSalters

1
Você pode ver facilmente usando o IDA Pro que a maioria das construções C ++ compila para a mesma coisa que fazê-lo manualmente em C, construtores e dtors ficam embutidos para objetos triviais, então a otimização futura é aplicada
paulm

7

K&R significa que a maioria das expressões C (significado técnico) mapeiam para uma ou algumas instruções de montagem, não uma chamada de função para uma biblioteca de suporte. As exceções usuais são divisão inteira em arquiteturas sem uma instrução div de hardware ou ponto flutuante em máquinas sem FPU.

Há uma citação:

C combina a flexibilidade e o poder da linguagem assembly com a facilidade de uso da linguagem assembly.

( encontrado aqui . Achei que me lembrava de uma variação diferente, como "velocidade da linguagem assembly com a conveniência e expressividade da linguagem assembly".)

long int geralmente tem a mesma largura dos registradores da máquina nativa.

Algumas linguagens de nível superior definem a largura exata de seus tipos de dados e as implementações em todas as máquinas devem funcionar da mesma forma. Mas não C.

Se você deseja trabalhar com ints de 128 bits em x86-64 ou, no caso geral, BigInteger de tamanho arbitrário, você precisa de uma biblioteca de funções para ele. Todas as CPUs agora usam complemento 2s como a representação binária de inteiros negativos, mas mesmo assim não era o caso quando C foi projetado. (É por isso que algumas coisas que dariam resultados diferentes em máquinas sem complemento de 2s são tecnicamente indefinidas nos padrões C.)

Os ponteiros C para dados ou funções funcionam da mesma maneira que endereços de assembly.

Se você deseja referências contadas, você mesmo deve fazer isso. Se você quiser funções-membro virtuais c ++ que chamem uma função diferente dependendo do tipo de objeto para o qual seu ponteiro está apontando, o compilador C ++ precisa gerar muito mais do que apenas uma callinstrução com um endereço fixo.

Strings são apenas matrizes

Fora das funções de biblioteca, as únicas operações de string fornecidas são ler / gravar um caractere. Sem concat, sem substring, sem pesquisa. (Strings são armazenadas como '\0'matrizes nul-terminated ( ) de inteiros de 8 bits, não ponteiro + comprimento, então para obter uma substring você teria que escrever um nul na string original.)

As CPUs às vezes têm instruções projetadas para uso por uma função de pesquisa de string, mas ainda geralmente processam um byte por instrução executada, em um loop. (ou com o prefixo x86 rep. Talvez se C fosse projetado em x86, a pesquisa ou comparação de string seria uma operação nativa, em vez de uma chamada de função de biblioteca.)

Muitas outras respostas fornecem exemplos de coisas que não são suportadas nativamente, como tratamento de exceções, tabelas hash, listas. A filosofia de design da K&R é a razão pela qual C não tem nenhum desses nativamente.


"K&R significa que a maioria das expressões C (significado técnico) mapeiam para uma ou algumas instruções de montagem, não uma chamada de função para uma biblioteca de suporte." Esta é uma explicação muito intuitiva. Obrigado.
gwg

1
Acabei de encontrar o termo "linguagem de von Neumann" ( en.wikipedia.org/wiki/Von_Neumann_programming_languages ). É EXATAMENTE o que C é.
Peter Cordes

1
É exatamente por isso que uso C. Mas o que me surpreendeu quando aprendi C é que, ao tentar ser eficiente para uma ampla variedade de hardware, às vezes é inepto e ineficiente na maioria dos hardwares modernos. Quero dizer, por exemplo, nenhuma maneira útil e confiável de detectar estouro de inteiro em c e adição de várias palavras usando a bandeira de transporte .
Bóson Z de

6

A linguagem assembly de um processo geralmente lida com jump (ir para), instruções, instruções move, artrítica binária (XOR, NAND, AND OR, etc), campos de memória (ou endereço). Categoriza a memória em dois tipos, instrução e dados. Isso é tudo que uma linguagem assembly é (tenho certeza de que os programadores de assembly vão argumentar que há mais do que isso, mas se resume a isso em geral). C se assemelha muito a essa simplicidade.

C é reunir o que a álgebra está para a aritmética.

C encapsula os fundamentos da montagem (a linguagem do processador). Provavelmente é uma afirmação mais verdadeira do que "Porque os tipos de dados e estruturas de controle fornecidos por C são suportados diretamente pela maioria dos computadores"


5

Cuidado com as comparações enganosas

  1. A declaração se baseia na noção de uma "biblioteca de tempo de execução" , que saiu de moda desde então, pelo menos para as linguagens de alto nível convencionais. (Ainda é relevante para os menores sistemas incorporados.) O tempo de execução é o suporte mínimo que um programa nessa linguagem requer para ser executado quando você usa apenas construções integradas na linguagem (em oposição a chamar explicitamente uma função fornecida por uma biblioteca) .
  2. Em contraste, as linguagens modernas tendem a não discriminar entre o tempo de execução e a biblioteca padrão , sendo a última frequentemente bastante extensa.
  3. Na época do livro K&R, C nem tinha uma biblioteca padrão . Em vez disso, as bibliotecas C disponíveis diferiam um pouco entre os diferentes sabores do Unix.
  4. Para entender a instrução, você não deve comparar as linguagens com uma biblioteca padrão (como Lua e Python mencionadas em outras respostas), mas com linguagens com construções mais integradas (como o LISP antigo e o FORTRAN antigo mencionado em outro respostas). Outros exemplos seriam BASIC (interativo, como LISP) ou PASCAL (compilado, como FORTRAN), que possuem (entre outras coisas) recursos de entrada / saída embutidos na própria linguagem.
  5. Em contraste, não existe uma maneira padrão de obter os resultados dos cálculos de um programa C que usa apenas o tempo de execução, não qualquer biblioteca.

Por outro lado, a maioria das linguagens modernas é executada em ambientes de tempo de execução dedicados que fornecem recursos como coleta de lixo.
Nate CK de

5

Existe um exemplo de tipo de dados ou estrutura de controle que não é suportado diretamente por um computador?

Todos os tipos de dados fundamentais e suas operações na linguagem C podem ser implementados por uma ou algumas instruções em linguagem de máquina sem loop - eles são diretamente suportados pela CPU (praticamente todas).

Vários tipos de dados populares e suas operações requerem dezenas de instruções em linguagem de máquina ou requerem a iteração de algum loop de tempo de execução, ou ambos.

Muitas linguagens têm sintaxe abreviada especial para esses tipos e suas operações - usar esses tipos de dados em C geralmente requer a digitação de muito mais código.

Esses tipos de dados e operações incluem:

  • manipulação de string de texto de comprimento arbitrário - concatenação, substring, atribuição de uma nova string a uma variável inicializada com alguma outra string, etc. ('s = "Hello World!"; s = (s + s) [2: -2] 'em Python)
  • conjuntos
  • objetos com destruidores virtuais aninhados, como em C ++ e qualquer outra linguagem de programação orientada a objetos
  • Multiplicação e divisão de matrizes 2D; resolução de sistemas lineares ("C = B / A; x = A \ b" no MATLAB e em muitas linguagens de programação de matriz)
  • expressões regulares
  • arrays de comprimento variável - em particular, anexar um item ao final do array, o que (às vezes) requer a alocação de mais memória.
  • lendo o valor de variáveis ​​que mudam de tipo em tempo de execução - às vezes é um float, outras vezes é uma string
  • matrizes associativas (geralmente chamadas de "mapas" ou "dicionários")
  • listas
  • ratios ("(+ 1/3 2/7)" dá "13/21" em Lisp )
  • aritmética de precisão arbitrária (muitas vezes chamada de "bignums")
  • converter dados em uma representação imprimível (o método ".tostring" em JavaScript)
  • saturação de números de ponto fixo (frequentemente usados ​​em programas C embutidos)
  • avaliar uma string digitada em tempo de execução como se fosse uma expressão ("eval ()" em muitas linguagens de programação).

Todas essas operações requerem dezenas de instruções em linguagem de máquina ou requerem a iteração de algum loop de tempo de execução em quase todos os processadores.

Algumas estruturas de controle populares que também requerem dezenas de instruções em linguagem de máquina ou loop incluem:

  • fechamentos
  • continuações
  • exceções
  • avaliação preguiçosa

Quer seja escrito em C ou em alguma outra linguagem, quando um programa manipula tais tipos de dados, a CPU deve eventualmente executar todas as instruções necessárias para manipular esses tipos de dados. Essas instruções geralmente estão contidas em uma "biblioteca". Cada linguagem de programação, até mesmo C, tem uma "biblioteca de tempo de execução" para cada plataforma incluída por padrão em cada executável.

A maioria das pessoas que escrevem compiladores coloca as instruções para manipular todos os tipos de dados que são "embutidos na linguagem" em sua biblioteca de tempo de execução. Como C não tem nenhum dos tipos de dados e operações e estruturas de controle acima incorporadas à linguagem, nenhum deles está incluído na biblioteca de tempo de execução C - o que torna a biblioteca de tempo de execução C menor do que a biblioteca de tempo de execução biblioteca de tempo de outras linguagens de programação que têm mais das coisas acima embutidas na linguagem.

Quando um programador deseja que um programa - em C ou qualquer outra linguagem de sua escolha - manipule outros tipos de dados que não estão "embutidos na linguagem", esse programador geralmente diz ao compilador para incluir bibliotecas adicionais com aquele programa, ou às vezes (para "evitar dependências") escreve outra implementação dessas operações diretamente no programa.


Se sua implementação do Lisp for avaliada (+ 1/3 2/7) como 3/21, acho que você deve ter uma implementação particularmente criativa ...
RobertB

4

Em que estão os tipos de dados integrados C? Eles são coisas como int, char, * int, float, matrizes etc ... Esses tipos de dados são entendidas pela CPU. A CPU sabe como trabalhar com arrays, como desreferenciar ponteiros e como fazer aritmética em ponteiros, inteiros e números de ponto flutuante.

Mas quando você vai para linguagens de programação de nível mais alto, você construiu tipos de dados abstratos e construções mais complexas. Por exemplo, observe a vasta gama de classes integradas na linguagem de programação C ++. A CPU não entende classes, objetos ou tipos de dados abstratos, então o tempo de execução C ++ preenche a lacuna entre a CPU e a linguagem. Estes são exemplos de tipos de dados não suportados diretamente pela maioria dos computadores.


2
O x86 sabe trabalhar com alguns arrays, mas não todos. Para tamanhos de elementos grandes ou incomuns, ele precisará realizar aritmética de inteiros para converter um índice de matriz em um deslocamento de ponteiro. E em outras plataformas, isso é sempre necessário. E a ideia de que a CPU não entende as classes C ++ é risível. São apenas deslocamentos de ponteiro, como estruturas C. Você não precisa de um runtime para isso.
MSalters de

@MSalters sim, mas os métodos reais das classes de biblioteca padrão, como iostreams etc, são funções de biblioteca em vez de serem diretamente suportados pelo compilador. No entanto, as linguagens de nível superior com as quais eles provavelmente estavam comparando não eram C ++, mas linguagens contemporâneas, como FORTRAN e PL / I.
Random832

1
As classes C ++ com funções de membro virtuais se traduzem em muito mais do que apenas um deslocamento em uma estrutura.
Peter Cordes

4

Depende do computador. No PDP-11, onde C foi inventado, longera mal suportado (havia um módulo adicional opcional que você poderia comprar que suportava algumas, mas não todas as operações de 32 bits). O mesmo é verdade em vários graus em qualquer sistema de 16 bits, incluindo o IBM PC original. E da mesma forma para operações de 64 bits em máquinas de 32 bits ou em programas de 32 bits, embora a linguagem C na época do livro K&R não tivesse nenhuma operação de 64 bits. E, claro, houve muitos sistemas ao longo dos anos 80 e 90 [incluindo o 386 e alguns processadores 486], e até mesmo alguns sistemas embarcados hoje, que não suportavam diretamente a aritmética de ponto flutuante ( floatou double).

Para um exemplo mais exótico, algumas arquiteturas de computador suportam apenas ponteiros "orientados por palavra" (apontando para um inteiro de dois ou quatro bytes na memória), e ponteiros de byte ( char *ou void *) tiveram que ser implementados adicionando um campo de deslocamento extra. Esta questão entra em alguns detalhes sobre tais sistemas.

As funções de "biblioteca de tempo de execução" a que se refere não são as que você verá no manual, mas funções como essas, em uma biblioteca de tempo de execução de um compilador moderno , que são usadas para implementar as operações de tipo básicas que não são suportadas pela máquina . A biblioteca de tempo de execução a que os próprios K&R se referiam pode ser encontrada no site da The Unix Heritage Society - você pode ver funções como ldiv(distintas da função C de mesmo nome, que não existia na época), que é usada para implementar a divisão de Valores de 32 bits, que o PDP-11 não suportava mesmo com o add-on, e csv(e crettambém em csv.c) que salvam e restauram registros na pilha para gerenciar chamadas e retornos de funções.

Eles provavelmente também estavam se referindo à escolha de não oferecer suporte a muitos tipos de dados que não são diretamente suportados pela máquina subjacente, ao contrário de outras linguagens contemporâneas, como FORTRAN, que tinha semântica de array que não mapeia tão bem para o suporte de ponteiro subjacente da CPU como Matrizes de C. O fato de que as matrizes C são sempre indexadas por zero e sempre de tamanho conhecido em todas as classificações, mas o primeiro significa que não há necessidade de armazenar os intervalos de índice ou tamanhos das matrizes, e não há necessidade de ter funções de biblioteca de tempo de execução para acessá-los - o compilador pode simplesmente codificar a aritmética de ponteiro necessária.


3

A declaração significa simplesmente que os dados e estruturas de controle em C são orientados à máquina.

Existem dois aspectos a serem considerados aqui. Uma é que a linguagem C tem uma definição (padrão ISO) que permite latitude na forma como os tipos de dados são definidos. Isso significa que as implementações da linguagem C são feitas sob medida para a máquina . Os tipos de dados de um compilador C correspondem ao que está disponível na máquina que o compilador visa, porque a linguagem tem latitude para isso. Se uma máquina tiver um tamanho de palavra incomum, como 36 bits, o tipo intou longpode ser ajustado para estar de acordo com ele. Programas que presumem que inttem exatamente 32 bits serão interrompidos.

Em segundo lugar, devido a tais problemas de portabilidade, há um segundo efeito. De certa forma, a declaração do K&R tornou-se uma espécie de profecia autorrealizável , ou talvez ao contrário. Ou seja, os implementadores de novos processadores estão cientes da grande necessidade de suporte a compiladores C e sabem que existe uma grande quantidade de código C que assume que "todo processador se parece com um 80386". As arquiteturas são projetadas com C em mente: e não apenas com C em mente, mas com equívocos comuns sobre portabilidade em C também. Você simplesmente não pode mais introduzir uma máquina com bytes de 9 bits ou qualquer outra coisa para uso geral. Programas que assumem que o tipochartem exatamente 8 bits de largura. Apenas alguns programas escritos por especialistas em portabilidade continuarão a funcionar: provavelmente não o suficiente para reunir um sistema completo com uma cadeia de ferramentas, kernel, espaço do usuário e aplicativos úteis, com esforço razoável. Em outras palavras, os tipos C se parecem com o que está disponível no hardware porque o hardware foi feito para se parecer com algum outro hardware para o qual muitos programas C não portáveis ​​foram escritos.

Existe um exemplo de tipo de dados ou estrutura de controle que não é suportado diretamente por um computador?

Tipos de dados não suportados diretamente em muitas linguagens de máquina: inteiro de multi-precisão; lista vinculada; tabela hash; cadeia de caracteres.

Estruturas de controle não suportadas diretamente na maioria das linguagens de máquina: continuação de primeira classe; co-rotina / fio; gerador; manipulação de exceção.

Tudo isso requer um código de suporte de tempo de execução considerável criado usando várias instruções de uso geral e tipos de dados mais elementares.

C tem alguns tipos de dados padrão que não são suportados por algumas máquinas. Desde C99, C tem números complexos. Eles são feitos de dois valores de ponto flutuante e feitos para funcionar com rotinas de biblioteca. Algumas máquinas não possuem unidade de ponto flutuante.

Com relação a alguns tipos de dados, não é claro. Se uma máquina tem suporte para endereçamento de memória usando um registro como endereço base e outro como deslocamento escalado, isso significa que os arrays são um tipo de dados com suporte direto?

Além disso, por falar em ponto flutuante, há padronização: IEEE 754 ponto flutuante. Por que seu compilador C tem um doubleque concorda com o formato de ponto flutuante suportado pelo processador não é apenas porque os dois foram feitos para concordar, mas porque existe um padrão independente para essa representação.


2

Coisas como

  • Listas usadas em quase todas as linguagens funcionais.

  • Exceções .

  • Matrizes associativas (mapas) - incluídas, por exemplo, em PHP e Perl.

  • Coleta de lixo .

  • Tipos de dados / estruturas de controle incluídos em muitas linguagens, mas não são diretamente suportados pela CPU.


2

Suportado diretamente deve ser entendido como um mapeamento eficiente para o conjunto de instruções do processador.

  • O suporte direto para tipos inteiros é a regra, exceto para os tamanhos longos (pode exigir rotinas aritméticas estendidas) e tamanhos curtos (pode exigir mascaramento).

  • O suporte direto para tipos de ponto flutuante requer que uma FPU esteja disponível.

  • O suporte direto para campos de bits é excepcional.

  • Estruturas e matrizes exigem computação de endereço, com suporte direto até certo ponto.

  • Os ponteiros são sempre suportados diretamente por meio de endereçamento indireto.

  • goto / if / while / for / do são diretamente suportados por desvios incondicionais / condicionais.

  • switch pode ser diretamente suportado quando uma tabela de salto se aplica.

  • As chamadas de função são suportadas diretamente por meio dos recursos de pilha.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.