Atualmente, o C é considerado um idioma de baixo nível , mas nos anos 70 era considerado de baixo nível? O termo estava em uso então?
Muitas linguagens populares de nível superior não existiam até meados dos anos 80 e além, por isso estou curioso se e como a natureza do nível baixo mudou ao longo dos anos.
— joeyfb
Como um ponto de dados, por volta de 1978, alguns mentores de programação descreveram C para mim como uma linguagem assembly glorificada.
@BenCrowell Não sei ao certo o que "glorificado" significa no contexto de sua declaração, mas experimentei chamar C uma linguagem assembly universal (= independente de plataforma) .
Curiosamente, há um argumento a ser feito: C não é uma linguagem de baixo nível e é menos agora do que era nos anos 70, porque a máquina abstrata de C está mais distante do hardware moderno do que no PDP-11.
Ao pensar nisso, você também pode querer pensar na origem - o Unix é escrito em C, c é executado no Unix e o unix é compilado em outras plataformas. Tudo o que não era necessário para compilar o Unix era desnecessário, e o principal objetivo de C era facilitar a gravação / portabilidade de um compilador. Para esse propósito, era o nível exato exato, então não penso em C como alto ou baixo, como uma ferramenta para portar o Unix que, como quase todas as ferramentas do Unix, é extremamente adaptável a muitos problemas.
Vale ressaltar que o lisp foi inventado em 1958
Para responder aos aspectos históricos da pergunta:
A filosofia do design é explicada na linguagem de programação C, escrita por Brian Kernighan e pelo designer C Dennis Ritchie, o "K&R" de que você já deve ter ouvido falar. O prefácio da primeira edição diz
C não é uma linguagem de "nível muito alto", nem uma linguagem "grande" ...
C é uma linguagem relativamente "de baixo nível" ... C não fornece operações para lidar diretamente com objetos compostos, como cadeias de caracteres, conjuntos, listas ou matrizes. Não há operações que manipulam uma matriz ou sequência inteira ...
Embora a ausência de alguns desses recursos possa parecer uma grave deficiência, ... manter o idioma em um tamanho modesto traz benefícios reais.
(Só tenho a segunda edição de 1988, mas o comentário abaixo indica que o texto citado é o mesmo da primeira edição de 1978).
Então, sim, os termos "alto nível" e "baixo nível" estavam em uso na época, mas C foi projetado para se encaixar em algum lugar do espectro no meio. Era possível escrever código em C que fosse portátil em plataformas de hardware, e esse era o principal critério para saber se um idioma era considerado de alto nível na época. No entanto, C carecia de alguns recursos característicos de linguagens de alto nível, e essa foi uma decisão de design em favor da simplicidade.
— Gatkin
Esta é uma excelente resposta! Evidência histórica verificável + testemunho de um ator melhor informado sobre o significado de nível baixo e alto no período ao qual o OP se refere. A propósito, confirmo que as citações já estavam na edição de 1978.
Isso depende da sua definição de linguagem de alto e baixo nível. Quando C foi desenvolvido, qualquer coisa de nível superior ao assembly era considerada uma linguagem de alto nível. Essa é uma barra baixa para limpar. Mais tarde, essa terminologia mudou a tal ponto que hoje em dia alguns consideram até o Java uma linguagem de baixo nível.
Mesmo no cenário linguístico de alto nível dos anos 70, vale ressaltar que C é um nível bastante baixo. A linguagem C é basicamente B mais um sistema de tipos simples, e B não é muito mais que uma camada de sintaxe processual / estruturada conveniente para montagem. Como o sistema de tipos é um ajuste retroativo no idioma B não digitado, você ainda pode deixar anotações de tipo em alguns lugares e intserá assumido.
C conscientemente deixa de fora caros ou difíceis de implementar recursos que já estavam bem estabelecidos na época, como
funções ou fechamentos aninhados
OOP básico ou corotinas
sistemas de tipos mais expressivos (por exemplo, tipos com restrição de intervalo, tipos definidos pelo usuário, como tipos de registro, digitação forte, ...)
C tem alguns recursos interessantes:
suporte para recursão (como conseqüência de suas variáveis ​​automáticas baseadas em pilha, em comparação com idiomas em que todas as variáveis ​​têm vida útil global)
Tipos de dados definidos pelo usuário (estruturas e uniões) foram implementados logo após o lançamento inicial do C.
A representação de string de C (ponteiro para caracteres) é realmente uma enorme melhoria em relação a B, que codificou várias letras em uma palavra de máquina.
Os arquivos de cabeçalho de C eram um truque de eficiência para manter pequenas as unidades de compilação, mas também forneciam um sistema de módulo simples.
Ponteiros irrestritos no estilo de montagem e aritmética do ponteiro, em comparação com referências mais seguras. Os ponteiros são um recurso inerentemente inseguro, mas também muito útil para programação de baixo nível.
No momento em que o C foi desenvolvido, outras linguagens inovadoras, como COBOL, Lisp, ALGOL (em vários dialetos), PL / I, SNOBOL, Simula e Pascal já haviam sido publicadas e / ou eram amplamente utilizadas para domínios de problemas específicos. Mas a maioria desses idiomas existentes era destinada à programação de mainframe ou eram projetos de pesquisa acadêmica. Por exemplo, quando o ALGOL-60 foi projetado pela primeira vez como uma linguagem de programação universal, a tecnologia e a ciência da computação necessárias para implementá-lo ainda não existiam. Alguns destes (alguns dialetos ALGOL, PL / I, Pascal) também foram projetados para programação de baixo nível, mas eles tendiam a ter compiladores mais complexos ou eram muito seguros (por exemplo, nenhum ponteiro irrestrito). Pascal não possui um bom suporte para matrizes de comprimento variável.
Comparado a esses idiomas, o C rejeita recursos "elegantes" e caros, a fim de ser mais prático para o desenvolvimento de baixo nível. C nunca foi principalmente um projeto de pesquisa de design de linguagem. Em vez disso, era uma ramificação do desenvolvimento do kernel do Unix no minicomputador PDP-11 que era comparativamente limitado por recursos. Por seu nicho (uma linguagem minimalista de baixo nível para escrever o Unix com um compilador de passagem única que é fácil de portar) C se destacou absolutamente - e mais de 45 anos depois ainda é a língua franca da programação de sistemas.
Apenas uma pequena adição para as pessoas que desconhecem o que era necessário para a família ALGOL e os idiomas Pascal existentes: esses idiomas tinham funções lexicamente aninhadas nas quais era possível acessar a variável (local) declarada em funções externas. Isso significava que era necessário manter uma "exibição" - uma matriz de ponteiros para escopos lexicais externos - a cada chamada de função e retorno (que alterava o nível lexical) - ou você precisava encadear escopos lexicais na pilha e cada acesso variável necessário vários saltos indiretos na pilha para encontrá-lo. Caro! C descartou tudo isso. Eu ainda sinto falta disso.
@davidbak: O x86-64 System V ABI (também conhecido como convenção de chamada no Linux / OS X) define %r10como o "ponteiro de cadeia estática", que é exatamente o que você está falando. Para C, é apenas mais um registro de rascunho, mas acho que Pascal o usaria. (Funções aninhadas GNU C usá-lo para passar um ponteiro para o escopo externo quando tal função não inline (por exemplo, se você fizer um ponteiro de função a ele para que o compilador cria um trampolim de código de máquina na pilha): A aceitabilidade da regulares uso de R10 e R11 )
@PeterCordes Fascinating! Pascal ainda era amplamente usado quando o System V foi lançado (embora eu não saiba quando a ABI formal do SysV foi definida). Sua resposta vinculada é muito informativa.
C possui tipos definidos pelo usuário (struct / union). O restante desses "recursos" foram omitidos, suspeito, porque são de uso zero ou negativo (a menos que você esteja participando de um concurso de código ofuscado :-)), pois isso prejudica o objetivo de manter o idioma simples e expressivo .
— Jamesqf 01/07/19
@ jamesqf: mas muito cedo C não tinha atribuição de estrutura; Eu acho que você tem que copiar ou copiar os membros individualmente, em vez de escrever a = b;para copiar uma estrutura inteira da maneira que você pode na ISO C89. Portanto, no início do C, os tipos definidos pelo usuário eram definitivamente de segunda classe e só podiam ser passados ​​por referência como função args. Aversão de C a matrizes e também Por que o C ++ suporta a atribuição de matrizes por membros dentro de estruturas, mas geralmente não?
No início da década de 1970, C era uma deslumbrante lufada de ar fresco, usando construções modernas tão efetivamente que todo o sistema UNIX podia ser reescrito da linguagem assembly para C, com pouco espaço ou penalidade de desempenho. Na época, muitos contemporâneos se referiam a ele como uma linguagem de alto nível.
Os autores de C, principalmente Dennis Ritchie, foram mais cautelosos e, no artigo do Jornal Técnico do Sistema Bell, "C não é uma linguagem de alto nível". Com um sorriso irônico e pretendendo ser provocativo, Dennis Ritchie diria que era uma linguagem de baixo nível. O principal entre seus objetivos de projeto para C era manter o idioma próximo à máquina e ainda fornecer portabilidade, ou seja, independência da máquina.
Foi basicamente digitado e wrappers de melhor sintaxe para a montagem do PDP, se você me perguntar.
@R .. ... a própria linguagem de máquina 68000 suporta construções C, em parte graças às semelhanças entre o PDP-11 e o 68000 Compute! Magazine Julho 1986.
@einpoklum Eu costumo concordar com isso. Eu aprendi C em 1980, na universidade, no PDP-11 / 34a, e foi descrito por um dos profissionais como uma "linguagem assembly portátil". Provavelmente porque, além do PDP-11, tínhamos várias máquinas Superbrain CP / M em laboratório que tinham um compilador C disponível. en.wikipedia.org/wiki/Intertec_Superbrain
Também uma excelente resposta baseada em evidências históricas verificáveis ​​(uau! Havia uma pré-versão do K&R disponível por aí!).
@einpoklum Isso não é exagero? Tive a oportunidade de escrever código de sistema e aplicativo em meados dos anos 80 no assembler (Z80 e M68K), um "assembler portátil" chamado M (sintaxe PDP11 com um conjunto abstrato de registro e instrução de 16 bits) e C. Em comparação com o assembler , C é definitivamente uma linguagem de alto nível: a produtividade da programação em C era uma ordem de magnitude superior à do assembler! Obviamente, sem strings (SNOBOL), sem suporte nativo a arquivos de dados (COBOL), sem código de geração automática (LISP), sem matemática avançada (APL), sem objetos (SIMULA), para que possamos concordar que não era muito alto
Como escrevi em outro lugar deste site, quando alguém se referia ao padrão de gerenciamento de memória malloc / free como "programação de baixo nível"
Engraçado como a definição de "nível baixo" muda com o tempo. Quando eu estava aprendendo a programar, qualquer idioma que fornecesse um modelo de heap padronizado que possibilitasse um simples padrão de alocação / livre foi considerado de alto nível. Na programação de baixo nível, você mesmo deve acompanhar a memória (não as alocações, mas as próprias localizações da memória!), Ou escrever seu próprio alocador de heap, se estiver realmente interessado.
Por um contexto, isso foi no início dos anos 90, bem após o lançamento de C.
A biblioteca padrão não era apenas uma coisa desde a padronização no final dos anos 80 (bem, com base nas APIs Unix existentes)? Além disso, a programação do kernel (que era o domínio de problemas original de C) naturalmente requer coisas de baixo nível, como gerenciamento manual de memória. Na época, C deveria ter sido a linguagem de programação de mais alto nível em que um kernel sério foi escrito (acho que hoje em dia o kernel do NT também usa uma boa quantidade de C ++).
@hyde, usei o Algol 60, dois dialetos FORTRAN diferentes e vários dialetos BASIC diferentes na década de 1970, e nenhum desses idiomas tinha ponteiros ou um alocador de heap.
A rigor, você ainda pode programar sem malloc()chamar diretamente brk(2)ou mmap(2)gerenciar a memória resultante. É uma PITA enorme, sem nenhum benefício concebível (a menos que você esteja implementando algo semelhante ao malloc), mas você pode fazê-lo.
@amon - exceto pelas notáveis ​​máquinas Burroughs baseadas em pilha que foram programadas no ALGOL de baixo para cima ... e muito antes do Unix também. Ah, e por falar nisso, Multics, que foi a inspiração para o Unix: Escrito em PL / I. Semelhante a ALGOL, nível mais elevado do que C.
Na verdade, a razão pela qual muitos de nós não estamos usando o Multics hoje provavelmente tem mais a ver com o fato de que ele só rodava em mainframes caríssimos - ou seja, a despesa ultrajante típica do dia a dia do mainframe mais a despesa extra de meio gabinete de hardware especializado para implementar a memória virtual com segurança. Quando minicomputadores de 32 bits como o VAX-11 foram lançados, todos, exceto os bancos e o governo, abandonaram a IBM e os Seven Dwarfs e levaram seu processamento em larga escala para "mini" computadores.
Muitas respostas já se referiram a artigos anteriores que diziam coisas como "C não é uma linguagem de alto nível".
Porém, não resisto a acumular: muitas, senão a maioria ou todas as HLLs da época - Algol, Algol-60, PL / 1, Pascal - forneceram verificação de limites de matriz e detecção numérica de estouro.
A última vez que verifiquei o estouro de buffer e número inteiro foi a causa raiz de muitas vulnerabilidades de segurança. ... Sim, ainda é o caso ...
A situação do gerenciamento dinâmico de memória era mais complicada, mas ainda assim, o malloc / free no estilo C foi um grande retrocesso em termos de segurança.
Portanto, se sua definição de HLL inclui "automaticamente impede muitos bugs de baixo nível", bem, o triste estado de segurança cibernética seria muito diferente, provavelmente melhor, se C e UNIX não tivessem acontecido.
Como, por acaso, estive envolvido na implementação do Intel MPX e no compilador de verificação de ponteiros / limites que surgiu, posso indicar os documentos sobre o desempenho deles: essencialmente 5-15%. Muito disso envolve análises de compiladores quase impossíveis nas décadas de 1970 e 1980 - em comparação com verificações ingênuas que podem ser 50% mais lentas. No entanto, acho justo dizer que C e UNIX atrasaram o trabalho dessas análises em 20 anos - quando C se tornou a linguagem de programação mais popular, havia muito menos demanda por segurança.
@jamesqf Além disso, muitas máquinas anteriores ao C tinham suporte especial de hardware para verificação de limites e estouros de número inteiro. Como C não usou esse HW, ele foi descontinuado e removido.
@jamesqf Por exemplo: o MIPS RISC ISA originalmente era baseado nos benchmarks de Stanford, originalmente escritos em Pascal, apenas posteriormente transferidos para C. Como Pascal verificou o estouro de número inteiro assinado, o mesmo aconteceu com o MIPS, em instruções como ADD. Por volta de 2010, eu estava trabalhando no MIPS, meu chefe queria remover instruções não usadas no MIPSr6, e estudos mostraram que as verificações de estouro quase nunca eram usadas. Mas o Javascript fez essas verificações - mas não conseguiu usar as instruções baratas por causa da falta de suporte do SO.
@KrazyGlew - É muito interessante trazer isso à tona, pois em C / C ++ a briga entre usuários e escritores de compiladores por "comportamento indefinido" devido ao estouro de número inteiro assinado está esquentando, já que os escritores de compiladores de hoje adotaram o velho mantra "Cometer um erro programa pior não é pecado "e aumentou para 11. Muitas postagens no stackoverflow e em outros lugares refletem isso ...
Se Rust tivesse intrínsecas SIMD como C, poderia ser uma linguagem de montagem portátil moderna quase ideal. C está ficando cada vez pior como uma linguagem de montagem portátil por causa da otimização agressiva baseada em UB e falha em expor de maneira portável novas operações primitivas suportadas por CPUs modernas (como popcnt, contar zeros à esquerda / à direita, inversão de bits, inversão de bytes, saturação) matemática). Conseguir que os compiladores C tornem asm eficientes para estes em CPUs com suporte a HW geralmente requer intrínsecas não portáteis. Ter o compilador emular popcnt nos destinos sem que seja melhor que o reconhecimento de idiomas popcnt.
Considere idiomas mais antigos e muito mais altos que antecederam C (1972):
Fortran - 1957 (nível não muito mais alto que C)
Fortran IV - 1961 (nível não muito mais alto que C)
PL / 1-1964
Além disso, uma linguagem de nível médio como o RPG (1959), principalmente uma linguagem de programação para substituir os sistemas de registro de unidades baseados em painéis.
Nessa perspectiva, C parecia uma linguagem de nível muito baixo, apenas um pouco acima dos montadores de macros usados ​​nos mainframes da época. No caso dos mainframes da IBM, as macros do assembler eram usadas para acesso ao banco de dados, como BDAM (método básico de acesso ao disco), já que as interfaces do banco de dados não tinham sido portadas para Cobol (na época), resultando em um legado de uma mistura de montagem e montagem. Programas Cobol ainda em uso hoje em mainframes IBM.
Se você deseja listar idiomas mais antigos e mais altos, não se esqueça do LISP .
@Duplicador - adicionei à lista. Eu estava focado no que era usado nos mainframes IBM, e não me lembro do LISP ser tão popular, mas o APL também era uma linguagem de nicho para os mainframes IBM (via consoles de compartilhamento de tempo) e o IBM 1130. Semelhante ao LISP, o APL é um dos as linguagens de alto nível mais exclusivas, por exemplo, parecem o pouco de código necessário para criar o jogo da vida de Conway com uma versão atual do APL: youtube.com/watch?v=a9xAKttWgP4 .
Eu não consideraria RPG ou COBOL como particularmente de alto nível, pelo menos não antes do COBOL-85. Quando você arranha a superfície do COBOL, vê que ele é essencialmente uma coleção de macros de assembler muito avançadas. Para começar, faltam funções, procedimentos e recursão, além de qualquer tipo de escopo. Todo o armazenamento deve ser declarado na parte superior do programa, levando a aberturas extremamente longas ou reutilização variável dolorosa.
— Idragge
Eu tenho algumas más recordações de usar Fortran IV, não me lembro de ser sensivelmente "nível mais elevado" do que C.
@idrougge - COBOL e RPG, e também conjuntos de instruções de mainframe incluem suporte completo para BCD empacotado ou não empacotado, um requisito para software financeiro em países como os EUA. Considero que os operadores nativos relacionados, como "mover correspondente", são de alto nível. O RPG era incomum, pois você especificou a ligação entre os campos de entrada brutos e os campos de saída formatados e / ou acumuladores, mas não a ordem das operações, semelhante à programação do painel substituída.
— Rcgldr 3/07
A resposta para sua pergunta depende de qual idioma C está sendo perguntado.
A linguagem descrita no Manual de Referência C de Dennis Ritchie, em 1974, era uma linguagem de baixo nível que oferecia algumas das conveniências de programação de linguagens de nível superior. Os dialetos derivados dessa linguagem também tendiam a ser linguagens de programação de baixo nível.
Quando o Padrão C 1989/1990 foi publicado, no entanto, ele não descreveu a linguagem de baixo nível que se tornou popular na programação de máquinas reais, mas descreveu uma linguagem de nível superior que poderia ser - mas não era necessário - implementado em termos de nível inferior.
Como observam os autores da Norma C, uma das coisas que tornou a linguagem útil foi o fato de muitas implementações poderem ser tratadas como montadoras de alto nível. Como o C também era usado como uma alternativa a outras linguagens de alto nível e como muitos aplicativos não exigiam a capacidade de fazer coisas que as linguagens de alto nível não podiam fazer, os autores do Padrão permitiram que as implementações se comportassem de maneira arbitrária. se os programas tentassem usar construções de baixo nível. Conseqüentemente, a linguagem descrita pelo Padrão C nunca foi uma linguagem de programação de baixo nível.
Para entender essa distinção, considere como o Ritchie's Language e o C89 visualizariam o trecho de código:
em uma plataforma em que "char" é 8 bits, "int" é big-endian de 16 bits, "float" é de 32 bits e as estruturas não têm requisitos especiais de preenchimento ou alinhamento, portanto o tamanho de "struct foo" é de 8 bytes.
No idioma de Ritchie, o comportamento da última instrução pegaria o endereço armazenado em "p", acrescentaria 3 * 8 + 2 [ie 26] bytes a ele e buscaria um valor de 16 bits nos bytes naquele endereço e no próximo , adicione um a esse valor e, em seguida, escreva de volta esse valor de 16 bits nos mesmos dois bytes. O comportamento seria definido como ação nos bytes 26 e 27 após o endereço p, sem levar em consideração o tipo de objeto armazenado lá.
Na linguagem definida pelo Padrão C, no caso de * p identificar um elemento de uma "estrutura foo []" que é seguida por pelo menos três elementos mais completos desse tipo, a última instrução adicionaria um ao membro y de o terceiro elemento após * p. O comportamento não seria definido pela Norma em nenhuma outra circunstância.
A linguagem de Ritchie era uma linguagem de programação de baixo nível porque, embora permitisse ao programador usar abstrações como matrizes e estruturas quando conveniente, definia o comportamento em termos do layout subjacente dos objetos na memória. Por outro lado, a linguagem descrita pelos padrões C89 e posteriores define as coisas em termos de uma abstração de nível superior e apenas define o comportamento do código que é consistente com isso. Implementações de qualidade adequadas para programação de baixo nível se comportarão de maneira útil em mais casos do que as exigidas pela Norma, mas não há documento "oficial" especificando o que uma implementação deve fazer para ser adequada a esses propósitos.
A linguagem C inventada por Dennis Ritchie é, portanto, uma linguagem de baixo nível e foi reconhecida como tal. A linguagem inventada pelo Comitê de Padrões C, no entanto, nunca foi uma linguagem de baixo nível na ausência de garantias fornecidas pela implementação que vão além dos mandatos da Norma.