Lançar uma exceção de ponteiro nulo [fechada]

14

Sua tarefa é gerar uma exceção de ponteiro nulo. Ou seja, seu programa deve aceitar um valor que espera ser não nulo e lançar uma exceção / erro ou falha porque o valor é nulo.

Além disso, não pode ser óbvio lendo o código que o valor é nulo. Seu objetivo é deixar claro para o leitor que o valor não é nulo, mesmo que seja.

  • Em vez de nulo, você pode usar nulo, nenhum, nada ou qualquer outro equivalente no seu idioma. Você também pode usar indefinido, não inicializado e assim por diante.
  • O problema com o seu código deve ser que a variável é (surpreendentemente) nula onde o programa espera uma variável não nula.
  • Seu programa pode responder ao nulo lançando uma exceção, lançando um erro, travando ou o que normalmente ocorre quando um nulo inesperado é encontrado.

Este é um concurso de popularidade, então seja esperto!

Ypnypn
fonte
@ Oururous Você pode dar um exemplo para mostrar o que você quer dizer?
Ypnypn
Depois de analisá-lo, é mais um erro de conversão do que você está procurando.
Οurous
Posso usar um bug do compilador?
Mark
1
@ Mark É um concurso de popularidade; deixe a comunidade decidir. Definitivamente votaria em um bug do compilador.
11684
Estou encerrando esta questão porque os concursos secretos estão fora do tópico por consenso da comunidade .
Dennis19 /

Respostas:

33

Java

Vamos calcular o valor absoluto de um número. Java possui Math.abs para essa finalidade, no entanto, o número que estamos usando pode ser nulo às vezes. Portanto, precisamos de um método auxiliar para lidar com esse caso:

public class NPE {
    public static Integer abs(final Integer x) {
        return x == null ? x : Math.abs(x);
    }

    public static void main(final String... args) {
        System.out.println(abs(null));
    }
}

Se x for nulo, retorne nulo; caso contrário, use Math.abs ().
O código é muito simples e claro, e deve funcionar bem ... certo?

A propósito, usando esta linha:

return x == null ? null : Math.abs(x);

funciona corretamente. Pensei em fazer disso um spoiler, mas ... acho que é tão intrigante :)

Ok, uma explicação:

Primeiro, Math.abs não usa um número inteiro, mas um int (também existem métodos sobrecarregados para outros tipos numéricos) e também retorna um int. Em java, int é um tipo primitivo (e não pode ser nulo) e Integer é sua classe correspondente (e pode ser nulo). Desde a versão 5 do java, uma conversão entre int e Inteiro é realizada automaticamente quando necessário. Portanto, o Math.abs pode pegar x, convertido automaticamente em int e retornar um int.

Agora a parte estranha: quando o operador ternário (? :) tem que lidar com 2 expressões em que um tem um tipo primitivo e o outro tem sua classe correspondente (como int e Integer), seria de esperar que o java convertesse o primitivo para a classe (também conhecida como "boxe"), especialmente quando o tipo necessário (aqui para retornar do método) é o tipo de referência (classe) e a primeira expressão também é do tipo de referência. Mas o java faz exatamente o oposto: converte o tipo de referência no tipo primitivo (também conhecido como "unboxing"). Portanto, no nosso caso, ele tentaria converter x em int, mas int não pode ser nulo; portanto, lança um NPE.

Se você compilar esse código usando o Eclipse, receberá um aviso: "Acesso nulo ao ponteiro: esta expressão do tipo Inteiro é nula, mas requer desmarcação automática"


/programming/7811608/java-npe-in-ternary-operator-with-autoboxing
/programming/12763983/nullpointerexception-through-auto-boxing-behavior-of-java -ternary-operator

aditsu
fonte
SPOILERS O que acontece quando x! = Null? (Eu descobri-lo já.)
11684
@ 11684 quando x não é nulo, ele funciona bem
aditsu 17/05
Então acho que não sei como isso funciona. Se ele funciona quando x não é nulo e eu estou certo sobre o motivo disso, o cólon deve ser analisado em dois sentidos (o que eu não colocaria em uma Especificação de idioma).
11684
@ 11684 não sei o que você quer dizer com "dois sentidos", mas enfim adicionei uma explicação agora
aditsu
23

C

Todo programador C cometeu esse erro, pelo menos uma vez.

#include <stdio.h>

int main(void) {
    int n=0;
    printf("Type a number : ");
    scanf("%d",n);
    printf("Next number is : %d",n+1);
    return 0;
}

Razão:

scanfpega um ponteiro ( int *) no argumento, aqui 0é passado (ponteiro NULL)

Consertar:

scanf("%d",&n);

http://ideone.com/MbQhMM

Michael M.
fonte
1
Não é exatamente assim. scanfé uma função variada e intfoi fornecido um argumento do tipo incorreto ( ), quando esperado int *. Como C (eu sei, os compiladores podem detectar isso no caso de scanf) não podem verificar os tipos na função variadica, isso é compilado felizmente. 0é de fato um ponteiro nulo literal, mas isso se aplica apenas ao literal usado diretamente, sem armazená-lo em uma variável. nnunca continha um ponteiro nulo.
Konrad Borowski
Você também precisa verificar o valor de retorno do scanf para corrigir esta função.
David Grayson
1
@ David Não é correto sem verificar o valor de retorno, mas não irá falhar ou invocar UB - (. Para ver isto acontecer, tente redirecionar um arquivo vazio como stdin) n permanecerá em 0.
Riking
14

PHP

Este me mordeu algumas vezes.

<?php

class Foo {
  private $bar;

  function init() {
    $this->bar = new Bar();
  }

  function foo() {
    $this->bar->display_greeting(); // Line 11
  }
}

class Bar {
  function display_greeting() {
    echo "Hello, World!";
  }
}

$foo_instance = new Foo();
$foo_instance->init();
$foo_instance->foo();

Resultado esperado:

Hello, World!

Resultado atual:

Fatal error: Call to a member function display_greeting() on a non-object on line 11

tcp NullPointerException

Razão:

Por padrão, a sintaxe do construtor é compatível com o PHP 4.x e, como tal, a função fooé um construtor válido para a classe Fooe, portanto, substitui o construtor vazio padrão. Esse tipo de erro pode ser evitado adicionando um espaço para nome ao seu projeto.

primo
fonte
9

CoffeeScript (no Node.js)

No CoffeeScript, ?é operador existencial. Se a variável existe, é usada, caso contrário, o lado direito está sendo usado. Todos sabemos que é difícil escrever programas portáteis. Caso em questão, a impressão em JavaScript está sub-especificada. Os navegadores usam alert(ou document.write), usos do escudo SpiderMonkey print, e usos Node.js console.log. Isso é loucura, mas o CoffeeScript ajuda com esse problema.

# Portable printer of "Hello, world!" for CoffeeScript

printingFunction = alert ? print ? console.log
printingFunction "Hello, world!"

Vamos executar isso em Node.js. Afinal, queremos garantir que nosso script funcione.

ReferenceError: print is not defined
  at Object.<anonymous> (printer.coffee:3:1)
  at Object.<anonymous> (printer.coffee:3:1)
  at Module._compile (module.js:456:26)

Uhm, por que você reclamaria disso, quando alerttambém não está definido?

Por alguma razão, no CoffeeScript, ?é deixado associativo, o que significa que ignora variáveis ​​indefinidas apenas para o lado esquerdo. Não foi corrigido, porque aparentemente alguns desenvolvedores podem depender? sendo deixado associativo .

Konrad Borowski
fonte
8

Rubi

Encontre awk no CAMINHO de um clone do Unix.

p = ENV['PATH'].split ':'

# Find an executable in PATH.
def find_exec(name)
  p.find {|d| File.executable? File.join(d, name)}
end

printf "%s is %s\n", 'awk', find_exec('awk')

Opa!

$ ruby21 find-awk.rb
find-awk.rb:5:in `find_exec': undefined method `find' for nil:NilClass (NoMethodError)
        from find-awk.rb:8:in `<main>'

Pelo erro, sabemos que p.findchamado nil.find, assim pdeve ser nil. Como isso aconteceu?

Em Ruby, deftem seu próprio escopo para variáveis ​​locais e nunca tira variáveis ​​locais do escopo externo. Assim, a atribuiçãop = ENV['PATH'].split ':' não está no escopo.

Uma variável indefinida geralmente causa NameError, mas pé um caso especial. Ruby tem um método global chamado p. Então p.find { ... }se torna uma chamada de método, como p().find { ... }. Quando pnão tem argumentos, ele retorna nil. (Os golfistas de código usam pcomo atalho para nil.) Em seguida, nil.find { ... }aumenta NoMethodError.

Eu o corrigi reescrevendo o programa em Python.

import os
import os.path

p = os.environ['PATH'].split(':')

def find_exec(name):
    """Find an executable in PATH."""
    for d in p:
        if os.access(os.path.join(d, name), os.X_OK,
                     effective_ids=True):
            return d
    return None

print("%s is %s" % ('awk', find_exec('awk')))

Funciona!

$ python3.3 find-awk.py 
awk is /usr/bin

Eu provavelmente quero imprimir awk is /usr/bin/awk, mas isso é um bug diferente.

Kernigh
fonte
7

C #

With()é um método de extensão para o stringobjeto, que é essencialmente apenas um alias para string.Format().

using System;

namespace CodeGolf
{
    internal static class Program
    {
        private static void Main()
        {
            Console.WriteLine( "Hello, {0}!".With( "World" ) );
        }

        private static string With( this string format, params object[] args )
        {
            Type str = Type.GetType( "System.string" );
            MethodInfo fmt = str.GetMethod( "Format", new[] { typeof( string ), typeof( object[] ) } );

            return fmt.Invoke( null, new object[] { format, args } ) as string;
        }
    }
}

Parece bom, certo? Errado.

Type.GetType()requer um nome de tipo totalmente qualificado e com distinção entre maiúsculas e minúsculas. O problema é que System.stringnão existe; stringé apenas um alias para o real tipo: System.String. Parece que deve funcionar, mas str.GetMethod()lançará a exceção porque str == null.

A maioria das pessoas que conhece um pouco sobre as especificidades internas do idioma provavelmente conseguirá identificar o problema rapidamente, mas ainda assim é algo que é facilmente esquecido de relance.

Tony Ellis
fonte
Essa é ótima: D
Knerd
Isso é um pouco óbvio demais. O leitor ter uma idéia imediata porque há duas maneiras de fazer a mesma coisa .GetType()e typeof()e, assim, levando a uma falha quando o resultado não é o mesmo. Eu acho que o pôster queria um código à prova de balas que não é.
Ja72
Por que a reflexão seria usada neste método de extensão? O código óbvio é return string.Format(format, args);. Se a reflexão fosse necessária (em outro caso de uso), alguém usaria typeof(string)(captura erros de maiúsculas e minúsculas no tempo de compilação, sem seqüência mágica), não o GetTypemétodo estático . Portanto, o exemplo parece "irrealista".
Jeppe Stig Nielsen
4

Unity3D

public GameObject asset;

Então você esquece de arrastar e soltar o ativo lá e BOOM, o Unity explode. Acontece o tempo todo.

Fabricio
fonte
3
espera, o desenvolvimento no unity3d requer um mouse?
Einacio 16/05
1
@Einacio, se você quiser coisas mais fáceis, basta arrastar e soltar um recurso para uma variável. Muito conveniente. Mas você pode codificar sem isso, se quiser.
Fabricio
4

Rubi

Apenas um código simples para obter o produto de uma matriz.

number_array = [2,3,9,17,8,11,14]
product = 1
for i in 0..number_array.length do
  product *= number_array[i]
end
puts product

Duas coisas aqui. Uma é que o ..operador da faixa é inclusivo . Portanto, 0..xpossui x + 1 elementos e inclui x. Isso significa que excedemos os limites da matriz. A outra coisa é que, quando você faz isso em Ruby, isso apenas lhe dá uma nilajuda. Isso é muito divertido quando, digamos, seu programa lança exceptdez linhas após o erro.

Hovercouch
fonte
Isso é óbvio demais. É como fazer for (int i = 0; i <= arr.length; i++)em Java.
Cole Johnson
3

Android

Eu vejo isso acontecer com muita frequência. Uma pessoa passa uma mensagem para a próxima atividade (talvez um código de status, dados do mapa, o que for) e acaba puxando um nulo do Intent.

À primeira vista, parece bastante razoável. Somente:

  • verifique se a mensagem não é nula
  • embalá-lo na intenção
  • iniciar nova atividade
  • ter intenção em nova atividade
  • extrair mensagem por tag

Em MenuActivity.java:

private void startNextActivity(String message){
    // don't pass a null!
    if(message == null)                        
        message = "not null";        

    // put message in bundle with tag "message"
    Bundle msgBundle = new Bundle();
    msgBundle.putString("message", message);   

    // pack it into a new intent
    Intent intent = new Intent(this, NextActivity.class);
    intent.putExtras(msgBundle);               
    startActivity(intent);
}

Em NextActivity.java:

private void handleMessage(){
    // get Intent
    Intent received = getIntent();
    if(received == null){
        Log.d("myAppTag","no intent? how does this even happen?");
        finish();
    }
    // get String with tag "message" we added in other activity
    String message = received.getStringExtra("message");
    if(message.length() > 10){
        Log.d("myAppTag", "my message is too long! abort!");
        finish();
    }
    // handle message here, etc
    // ...
    // too bad we never GET here!
}

FWIW, o JavaDoc faz dizer que Intent.getStringExtra(String)pode voltar null, mas somente se o tag não foi encontrado. Claramente , estou usando a mesma tag, então deve ser outra coisa ...

Geobits
fonte
2
O problema com esta resposta é que pessoas que não estão familiarizadas com o desenvolvimento do Android (como eu) não poderão apreciá-lo.
John Dvorak
@JanDvorak Concordou, mas não é grande coisa. Existem outras respostas no site que também não aprecio totalmente. :)
Geobits
3

C

Apenas uma rápida leitura do buffer, onde o programador foi gentil o suficiente para documentar o perigo potencial!

char* buffer;
int main( void )
{
    ///!!WARNING: User name MUST NOT exceed 1024 characters!!\\\
    buffer = (char*) malloc( 1024 );
    printf("Please Input Your Name:");
    scanf("%s", buffer);
}

Truque muito simples e óbvio, se você espera um código malicioso. O comentário que termina com '\' escapa à nova linha, portanto, o buffer nunca recebe memória alocada. Ele falhará no scanf, pois o buffer será NULL (como as variáveis ​​de escopo do arquivo são zero inicializadas em C).

Bilkokuya
fonte
0

Nimrod

type TProc = proc (a, b: int): int

proc func1(a, b: int): int=a+b
proc func2(a, b: int): int=a-b

proc make_func(arg: int, target: var TProc)=
  if arg == 1:
    target = func1
  elif arg == 2:
    target = func2
  else:
    raise newException(EIO, "abc")

var f, f2: TProc

try:
  make_func(2, f)
  make_func(3, f2)
except EIO:
  discard

echo f(1, 2)
echo f2(3, 4)

O que está acontecendo aqui é um pouco complicado. No Nimrod, os procedimentos são inicializados por padrão nil. Na primeira make_funcchamada, é bem-sucedida. O segundo, no entanto, lança uma exceção e sai f2não inicializado. É então chamado, causando um erro.

kirbyfan64sos
fonte
0

C #

Isto é clássico. O método muito útil FindStringRepresentationOfcriará uma nova instância do parâmetro de tipo especificado e, em seguida, encontrará a representação de string dessa instância.

Certamente será um desperdício verificar nullimediatamente após ter criado uma nova instância, por isso não fiz isso ...

static void Main()
{
  FindStringRepresentationOf<DateTime>();  // OK, "01/01/0001 00:00:00" or similar
  FindStringRepresentationOf<DateTime?>(); // Bang!
}

static string FindStringRepresentationOf<TNewable>() where TNewable : new()
{
  object goodInstance = new TNewable();
  return goodInstance.ToString();
}

Alterar objecta declaração da variável local TNewable para var(C # 3 e posterior) faz com que o problema desapareça. Dica: o boxe de Nullable<T>(aka T?) é anômalo no .NET Framework.

Depois de corrigir o problema, conforme descrito no texto invisível acima, tente também goodInstance.GetType()(a diferença é que GetType(), ao contrário ToString(), não é virtual, portanto não o podiam overrideno tipo Nullable<>).

Jeppe Stig Nielsen
fonte
0

C ++:

#include <iostream>
#include <cstring>
#include <vector>
#include <conio.h>
#include <string>

using namespace std;

class A
{
public:
    string string1;
    A()
    {
        string1 = "Hello World!";
    }
};
A *a_ptr;

void init()
{
    A a1;
    a_ptr = &a1;
}

int main()
{
    init();
    cout<<a_ptr->string1;
    _getch();
    return 0;
}

O que você esperaria é "Olá Mundo!" para ser impresso. Mas você verá apenas lixo na tela.

Aqui o a1 é destruído quando o escopo init () é concluído. Portanto, a_ptr, como aponta para a1, produzirá lixo.

wadf
fonte
0

C

#define ONE 0 + 1

int main(void) {
    printf("1 / 1 = %d\n", 1 / ONE);
    return 0;
}

Explicação

O pré-processador não calcula 0 + 1, de fato , ONEé literalmente definido como 0 + 1, resultando em 1 / 0 + 1uma divisão por zero e resulta em uma exceção de ponto flutuante.

nyuszika7h
fonte