Parece que houve uma mudança gradual no pensamento sobre o uso de ponteiros em linguagens de programação, de modo que se tornou geralmente aceito que os ponteiros eram considerados arriscados (se não completamente "maus" ou engrandecimentos semelhantes).
Quais foram os desenvolvimentos históricos dessa mudança de pensamento? Houve eventos específicos, seminais, pesquisas ou outros desenvolvimentos?
Por exemplo, uma retrospectiva superficial da transição de C para C ++ para Java parece mostrar uma tendência para complementar e depois substituir completamente os ponteiros por referências. No entanto, a verdadeira cadeia de eventos era provavelmente muito mais sutil e complexa do que isso, e não tão seqüencial. Os recursos que chegaram aos idiomas principais podem ter se originado em outros lugares, talvez muito antes.
Nota: Não estou perguntando sobre os méritos reais de ponteiros versus referências versus outra coisa. Meu foco está nas razões para essa aparente mudança.
Respostas:
A justificativa foi o desenvolvimento de alternativas aos indicadores.
Sob o capô, qualquer ponteiro / referência / etc está sendo implementado como um número inteiro contendo um endereço de memória (também conhecido como ponteiro). Quando C saiu, essa funcionalidade foi exposta como ponteiros. Isso significava que qualquer coisa que o hardware subjacente pudesse fazer para endereçar a memória poderia ser feita com ponteiros.
Isso sempre foi "perigoso", mas o perigo é relativo. Quando você está criando um programa de 1000 linhas ou quando possui procedimentos de qualidade de software de nível IBM, esse perigo pode ser facilmente resolvido. No entanto, nem todo software estava sendo desenvolvido dessa maneira. Como tal, surgiu um desejo por estruturas mais simples.
Se você pensar bem, um
int&
e umint* const
realmente têm o mesmo nível de segurança, mas um tem uma sintaxe muito mais agradável que o outro.int&
também poderia ser mais eficiente porque poderia se referir a um int armazenado em um registro (anacronismo: isso era verdade no passado, mas compiladores modernos são tão bons em otimizar que você pode ter um ponteiro para um número inteiro em um registro, desde que você nunca usa nenhum dos recursos que exigiriam um endereço real, como++
)À medida que avançamos para Java , passamos para linguagens que fornecem algumas garantias de segurança. C e C ++ não forneceram nenhum. Java garante que apenas operações legais sejam executadas. Para fazer isso, o java eliminou completamente os ponteiros. O que eles descobriram é que a grande maioria das operações de ponteiro / referência feitas em código real eram coisas pelas quais as referências eram mais do que suficientes. Somente em alguns casos (como iteração rápida através de uma matriz) os ponteiros eram realmente necessários. Nesses casos, o java leva um tempo de execução para evitar usá-los.
A mudança não foi monotônica. C # reintroduziu ponteiros, embora de uma forma muito limitada. Eles são marcados como " não seguros " , o que significa que não podem ser usados por código não confiável. Eles também têm regras explícitas sobre o que podem e não podem apontar (por exemplo, é simplesmente inválido incrementar um ponteiro após o final de uma matriz). No entanto, eles descobriram que havia um punhado de casos em que o alto desempenho dos ponteiros era necessário, então os colocaram de volta.
Também interessariam as linguagens funcionais, que não possuem esse conceito, mas essa é uma discussão muito diferente.
fonte
É necessário algum tipo de indireção para programas complexos (por exemplo, estruturas de dados recursivas ou de tamanho variável). No entanto, não é necessário implementar esse indireto por meio de ponteiros.
A maioria das linguagens de programação de alto nível (ou seja, não o Assembly) é bastante segura para a memória e não permite acesso irrestrito ao ponteiro. A família C é a mais estranha aqui.
C evoluiu de B, que era uma abstração muito fina sobre a montagem bruta. B tinha um único tipo: a palavra. A palavra pode ser usada como um número inteiro ou como ponteiro. Esses dois são equivalentes quando toda a memória é visualizada como uma única matriz contígua. C manteve essa abordagem bastante flexível e continuou a apoiar aritmética de ponteiro inerentemente insegura. Todo o sistema de tipos de C é mais uma reflexão tardia. Essa flexibilidade no acesso à memória tornou o C muito adequado para seu objetivo principal: criar um protótipo do sistema operacional Unix. É claro que o Unix e o C se mostraram bastante populares, de modo que o C também é usado em aplicativos em que essa abordagem de baixo nível da memória não é realmente necessária.
Se olharmos para as linguagens de programação que vieram antes do C (por exemplo, Fortran, dialetos de Algol, incluindo Pascal, Cobol, Lisp, ...), alguns deles suportam ponteiros do tipo C. Notavelmente, o conceito de ponteiro nulo foi inventado para o Algol W em 1965. Mas nenhuma dessas linguagens tentou ser uma linguagem eficiente de sistemas de baixa abstração do tipo C: o Fortran era destinado à computação científica, o Algol desenvolveu alguns conceitos bastante avançados, o Lisp foi mais um projeto de pesquisa do que uma linguagem de nível industrial, e Cobol estava focado em aplicativos de negócios.
A coleta de lixo existe desde o final dos anos 50, ou seja, bem antes de C (início dos anos 70). O GC exige que a segurança da memória funcione corretamente. Os idiomas antes e depois de C usavam o GC como um recurso normal. É claro que isso torna uma linguagem muito mais complicada e possivelmente mais lenta, o que era especialmente perceptível na época dos mainframes. As linguagens de GC tendem a ser orientadas para a pesquisa (por exemplo, Lisp, Simula, ML) e / ou requerem estações de trabalho poderosas (por exemplo, Smalltalk).
Com computadores menores e mais poderosos, a computação em geral e as linguagens de GC se tornaram mais populares. Para aplicações em tempo não real (e algumas vezes até então), o GC é agora a abordagem preferida. Mas os algoritmos de GC também foram objeto de intensa pesquisa. Como alternativa, uma melhor segurança de memória sem GC também foi desenvolvida, especialmente nas últimas três décadas: inovações notáveis são RAII e indicadores inteligentes no C ++ e no verificador vitalício do sistema / empréstimo da Rust.
O Java não inovou por ser uma linguagem de programação segura para a memória: ela basicamente utilizou a semântica da linguagem GCt, Smalltalk, segura para a memória e combinou-as com a sintaxe e a digitação estática do C ++. Foi então comercializado como um C / C ++ melhor e mais simples. Mas é apenas superficialmente um descendente de C ++. A falta de indicadores de Java é devida muito mais ao modelo de objeto Smalltalk do que à rejeição do modelo de dados C ++.
Portanto, linguagens "modernas" como Java, Ruby e C # não devem ser interpretadas como superando os problemas de ponteiros brutos como em C, mas devem ser vistas como extraídas de muitas tradições - incluindo C, mas também de linguagens mais seguras como Smalltalk, Simula, ou Lisp.
fonte
Na minha experiência, ponteiros sempre foram um conceito desafiador para muitas pessoas. Em 1970, a universidade em que eu freqüentava tinha um Burroughs B5500 e usamos o Algol estendido para nossos projetos de programação. A arquitetura do hardware foi baseada em descritores e em alguns códigos na parte superior das palavras de dados. Eles foram projetados explicitamente para permitir que as matrizes usem ponteiros sem ter permissão para sair do final.
Tínhamos discutido em sala de aula discussões sobre nome versus valor e como as matrizes B5500 funcionavam. Alguns de nós obtiveram a explicação imediatamente. Outros não.
Mais tarde, foi um choque que o hardware não me protegesse dos ponteiros fugitivos - especialmente na linguagem assembly. No meu primeiro emprego após a graduação, ajudei a corrigir problemas em um sistema operacional. Muitas vezes, a única documentação que tínhamos era o despejo de memória impresso. Eu desenvolvi um talento especial para encontrar a fonte de indicadores descontrolados nos despejos de memória, para que todos me dessem os despejos "impossíveis" para descobrir. Muitos dos problemas que tivemos foram causados por erros de ponteiro do que por qualquer outro tipo de erro.
Muitas das pessoas com quem trabalhei começaram a escrever FORTRAN, depois se mudaram para C, escreveram C muito parecidas com FORTRAN e evitaram sugestões. Como eles nunca internalizaram ponteiros e referências, o Java apresenta problemas. Freqüentemente, é um desafio entender para os programadores do FORTRAN como a atribuição de objetos realmente funciona.
As linguagens modernas tornaram muito mais fácil fazer coisas que precisam de indicadores "ocultos", mantendo-nos a salvo de erros de digitação e outros erros.
fonte