Pergunta

Em várias linguagens de programação modernas (incluindo C ++, Java e C #), a linguagem permite integer overflow para ocorrer em tempo de execução sem levantar qualquer tipo de condição de erro.

Por exemplo, considere este método # (artificial) C, que não leva em conta a possibilidade de estouro / estouro negativo. (Por questões de brevidade, o método também não lidar com o caso onde a lista especificada é uma referência nula.)

//Returns the sum of the values in the specified list.
private static int sumList(List<int> list)
{
    int sum = 0;
    foreach (int listItem in list)
    {
        sum += listItem;
    }
    return sum;
}

Se este método é chamado como se segue:

List<int> list = new List<int>();
list.Add(2000000000);
list.Add(2000000000);
int sum = sumList(list);

Um transbordamento irá ocorrer no método sumList() (porque o tipo int em C # é um inteiro de 32-bit, e a soma dos valores na lista excede o valor do máximo número inteiro assinado de 32 bits). A soma variável terá um valor de -294967296 (não um valor de 4000000000); isso provavelmente não é o que o (hipotético) desenvolvedor do método ListaDeSomas pretendido.

Obviamente, existem várias técnicas que podem ser usados ??pelos desenvolvedores para evitar a possibilidade de estouro de inteiros, como o uso de um tipo como BigInteger , ou o checked palavra-chave e /checked opção de compilador em C #.

No entanto, a pergunta que eu estou interessado é por isso que essas línguas foram projetados para permitir que por padrão inteiro transborda de acontecer, em primeiro lugar, em vez de, por exemplo, levantando uma exceção quando uma operação é executada em tempo de execução que resultar em um estouro. Parece que tal comportamento poderia ajudar a evitar erros nos casos em que um desenvolvedor deixa de conta para a possibilidade de estouro ao escrever código que executa uma operação aritmética que poderia resultar em excesso. (Estas línguas poderia ter incluído algo como uma palavra-chave "desmarcada", que poderia designar um bloco onde integer overflow é permitido ocorrer sem uma exceção a ser levantada, nos casos em que o comportamento é explicitamente destinados pelo desenvolvedor; C # realmente tem essa .)

Será que a resposta simplesmente se resumem a performance - os projetistas da linguagem não queria que seus respectivos idiomas como padrão para ter "lentos" operações com números inteiros aritméticos, onde o tempo de execução seria necessário para fazer trabalho extra para verificar se um estouro ocorreu, em cada operação aritmética aplicável - e esta consideração desempenho superou o valor de evitar falhas "silenciosas" no caso em que um estouro inadvertida ocorre

?

Existem outras razões para esta decisão design de linguagem, bem como, que não considerações de desempenho?

Foi útil?

Solução

Em C #, era uma questão de desempenho. Especificamente, out-of-box de benchmarking.

Quando C # era novo, a Microsoft estava esperando um monte de desenvolvedores de C ++ iria mudar para ele. Eles sabiam que muitas pessoas C ++ pensou em C ++ como sendo rápido, especialmente mais rápido do que as línguas que "desperdício" de tempo no gerenciamento automático de memória e similares.

Ambos os adotantes potenciais e revisores de revistas são susceptíveis de obter uma cópia do novo C #, instalá-lo, construir um aplicativo trivial que ninguém jamais iria escrever no mundo real, executá-lo em um loop apertado, e medir quanto tempo tomou. Então eles fazem uma decisão para a sua empresa ou publicar um artigo com base nesse resultado.

O fato de que seu teste mostrou C # para ser mais lento do que compilado nativamente C ++ é o tipo de coisa que poderia afastar as pessoas C # rapidamente. O fato de que seu aplicativo C # vai pegar estouro / estouro negativo automaticamente é o tipo de coisa que eles poderiam perder. Então, é desativado por padrão.

Eu acho que é óbvio que 99% do tempo que queremos / verificado estar. É um compromisso infeliz.

Outras dicas

Eu acho que o desempenho é uma razão muito boa. Se você considerar todas as instruções em um programa típico que incrementos de um inteiro, e se em vez de o op simples de adicionar 1, ele tinha que verificar cada vez que se adicionando 1 iria transbordar o tipo, então o custo em ciclos extra seria muito grave.

Você trabalha sob a suposição de que integer overflow é sempre um comportamento indesejado.

comportamento Às vezes integer overflow é desejada. Um exemplo que eu vi é a representação de um valor de título absoluto como um número de ponto fixo. Dado um unsigned int, 0 é 0 ou 360 graus e o máximo de 32 bit inteiro sem sinal (0xffffffff) é o maior valor um pouco abaixo de 360 ??graus.

int main()
{
    uint32_t shipsHeadingInDegrees= 0;

    // Rotate by a bunch of degrees
    shipsHeadingInDegrees += 0x80000000; // 180 degrees
    shipsHeadingInDegrees += 0x80000000; // another 180 degrees, overflows 
    shipsHeadingInDegrees += 0x80000000; // another 180 degrees

    // Ships heading now will be 180 degrees
    cout << "Ships Heading Is" << (double(shipsHeadingInDegrees) / double(0xffffffff)) * 360.0 << std::endl;

}

Há provavelmente outras situações onde estouro é aceitável, semelhante a este exemplo.

C / C ++ Nunca mandato comportamento armadilha. Mesmo a divisão óbvia por 0 é um comportamento indefinido em C ++, não um determinado tipo de armadilha.

A linguagem C não tem qualquer conceito de armadilhas, a menos que você conte sinais.

C ++ tem um princípio de design que ela não introduz sobrecarga não está presente em C a menos que você perguntar para ele. Então Stroustrup não teria queria mandato que inteiros se comportam de uma maneira que requer qualquer verificação explícita.

Alguns compiladores primeiros e implementações leves para hardware restrito, não suportam exceções em tudo, e exceções muitas vezes pode ser desativado com opções do compilador. A obrigatoriedade de exceções para a linguagem embutidos seria problemático.

Mesmo que C ++ havia feito inteiros marcada, 99% dos programadores nos primeiros dias teria virado se fora para o aumento de desempenho ...

Como a verificação de estouro leva tempo. Cada operação matemática primitiva, que normalmente se traduz em uma única instrução de montagem teria de incluir um cheque de excesso, resultando em múltiplas instruções de montagem, potencialmente resultando em um programa que é várias vezes mais lento.

É provável desempenho de 99%. Em x86 teria que verificar a bandeira estouro em cada operação que seria um enorme impacto na performance.

O outro 1% cobriria os casos em que as pessoas estão fazendo manipulações fantasia bit ou ser 'impreciso' em misturar assinado e operações sem sinal e quer a semântica estouro.

Retrocompatibilidade é um grande problema. Com C, assumiu-se que você estava prestando atenção suficiente para o tamanho de seus tipos de dados que se um over / underflow ocorreu, que era o que você queria. Em seguida, com C ++, C # e Java, muito pouco mudou com a forma como o "built-in" tipos de dados funcionou.

O meu entendimento de por que os erros não seriam levantadas por padrão em tempo de execução resume-se ao legado de desejando criar linguagens de programação com ácido semelhante comportamento. Especificamente, o princípio de que qualquer coisa que você código que ele faça (ou não código), ele vai fazer (ou não fazer). Se você não fez código algum manipulador de erro, em seguida, a máquina irá "assumir" em virtude de não manipulador de erro, que você realmente quer fazer a coisa ridícula, crash-propenso você está dizendo a ele para fazer.

(referência ACID: http://en.wikipedia.org/wiki/ACID )

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top