Quando e por que os ponteiros começaram a ser vistos como arriscados?

18

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.

DaveInCaz
fonte
1
Foi devido ao declínio da educação em Artes Liberais. As pessoas não podiam mais compreender a Referência Indireta, uma das idéias mais fundamentais em tecnologia de computação, incluída em todas as CPUs.
10
Ponteiros são arriscados. Por que você acha que houve uma mudança de pensamento? Houve melhorias nos recursos de linguagem e hardware que permitem escrever software sem ponteiros, embora não sem alguma penalidade de desempenho.
Pare de prejudicar Monica
4
@DaveInCaz Até onde eu sei, esse desenvolvimento específico foi a invenção de indicadores.
Pare de prejudicar Monica
5
@nocomprende: o que você acabou de escrever não é fatos nem evidências, apenas opinião. Havia muito menos programadores em 1970, você não tem nenhuma evidência de que a população hoje seja melhor ou pior na "referência indireta".
Whatsisname
3
Os ponteiros sempre foram considerados arriscados, desde o primeiro dia. Foi simplesmente um compromisso movê-los do assembly para idiomas de nível superior.
Frank Hileman

Respostas:

21

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 um int* constrealmente 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.

Cort Ammon - Restabelecer Monica
fonte
3
Não sei se é correto dizer que Java não tem ponteiros. Não quero entrar em um longo debate sobre o que é e o que não é um ponteiro, mas o JLS diz que "o valor de uma referência é um ponteiro". Simplesmente não é permitido acesso direto ou modificação de ponteiros. Isso também não é apenas para segurança; manter as pessoas fora do negócio de acompanhar onde um objeto está a qualquer momento é útil para o GC.
JimmyJames 26/09
6
@JimmyJames True. Para os fins desta resposta, a linha divisória entre ponteiro e não ponteiro era se ela suportava operações aritméticas de ponteiro, que normalmente não são suportadas por referências.
Cort Ammon - Restabelece Monica
8
@ JimmyJames Eu concordo com a afirmação de Cort de que um ponteiro é algo em que você pode executar operações aritméticas, enquanto uma referência não. O mecanismo real que implementa uma referência em linguagens como Java é um detalhe de implementação.
Robert Harvey
3
Em geral, C e C ++ aceitaram voluntariamente a participação nesse clube perigoso, permitindo muitos "comportamentos indefinidos" na especificação.
Rwong 26/09
2
By the way, não são CPUs, que distinguem entre ponteiros e números. Por exemplo, a CPU CISC original de 48 bits no IBM AS / 400 faz isso. E, de fato, há uma camada de abstração por baixo do sistema operacional, o que significa que não só distinguir a CPU entre os números e ponteiros e proibir aritmética em ponteiros, mas o próprio sistema operacional ainda não sabe sobre ponteiros em tudo e nem as línguas . Curiosamente, isso torna o sistema original do AS / 400 um sistema, onde a reescrita do código de uma linguagem de script de alto nível em C torna a ordem de magnitude mais lenta .
Jörg W Mittag
10

É 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.

amon
fonte
4

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.

Valerie Griffin
fonte