Genera un'eccezione puntatore null [chiuso]


14

Il tuo compito è generare un'eccezione puntatore null. Cioè, il tuo programma deve accettare un valore che si aspetta sia non nullo e generare un'eccezione / errore o arresto anomalo perché il valore è nullo.

Inoltre, dalla lettura del codice non può essere ovvio che il valore è null. Il tuo obiettivo è far sembrare chiaro al lettore che il valore non è nullo, anche se in realtà lo è.

  • Invece di null, puoi usare zero, none, nothing o qualunque sia l'equivalente nella tua lingua. È inoltre possibile utilizzare non definito, non inizializzato e così via.
  • Il problema con il codice deve essere che la variabile è (sorprendentemente) nulla in cui il programma prevede una variabile non nulla.
  • Il tuo programma può rispondere al null generando un'eccezione, generando un errore, un arresto anomalo o qualsiasi altra cosa normalmente quando si verifica un null imprevisto.

Questo è un concorso di popolarità, quindi sii intelligente!


@Ourous Puoi fare un esempio per mostrare cosa intendi?
Ypnypn,

Dopo averlo esaminato, è più un errore di cast di quello che stai cercando.
Οuroso

Posso usare un bug del compilatore?
Segna

1
@Mark È un concorso di popolarità; lascia decidere alla community. Vorrei sicuramente votare per un bug del compilatore.
11684,

Risposte:


33

Giava

Calcoliamo il valore assoluto di un numero. Java ha Math.abs per questo scopo, tuttavia il numero che stiamo usando potrebbe essere nullo a volte. Quindi abbiamo bisogno di un metodo di supporto per affrontare quel 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 è null, restituisce null, altrimenti usa Math.abs ().
Il codice è molto semplice e chiaro e dovrebbe funzionare bene ... giusto?

A proposito, usando questa linea invece:

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

funziona correttamente. Ho pensato di renderlo uno spoiler, ma .. penso che sia altrettanto sconcertante :)

Ok, una spiegazione:

Innanzitutto, Math.abs non accetta un numero intero, ma un int (esistono anche metodi sovraccaricati per altri tipi numerici) e restituisce anche un int. In java, int è un tipo primitivo (e non può essere nullo) e Integer è la sua classe corrispondente (e può essere null). Dalla versione 5 di Java, una conversione tra int e Integer viene eseguita automaticamente quando necessario. Quindi Math.abs può prendere x, convertito automaticamente in int e restituire un int.

Ora la parte strana: quando l'operatore ternario (? :) ha a che fare con 2 espressioni in cui una ha un tipo primitivo e l'altra ha la sua classe corrispondente (come int e Integer), ci si aspetterebbe che Java converta la primitiva alla classe (alias "boxing"), specialmente quando il tipo di cui ha bisogno (qui per tornare dal metodo) è il tipo di riferimento (classe), e anche la prima espressione è del tipo di riferimento. Ma java fa esattamente il contrario: converte il tipo di riferimento nel tipo primitivo (aka "unboxing"). Quindi nel nostro caso, proverebbe a convertire x in int, ma int non può essere nullo, quindi genera un NPE.

Se compili questo codice usando Eclipse, riceverai effettivamente un avviso: "Accesso puntatore nullo: questa espressione di tipo Integer è nulla ma richiede un unboxing automatico"


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



SPOILER Cosa succede quando x! = Null? (L'ho già capito.)
11684,

@ 11684 quando x non è null, funziona benissimo
aditsu smesso perché SE è EVIL

Quindi penso di non sapere come funziona. Se funziona quando x non è nullo e ho ragione sul perché questo genera, i due punti devono essere analizzati in due sensi (che non inserirei in una specifica della lingua).
11684,

@ 11684 non sono sicuro di cosa intendi per "due sensi", ma comunque ho aggiunto una spiegazione ora
aditsu smetti perché SE è MALE

23

C

Ogni programmatore C ha commesso questo errore, almeno una volta.

#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;
}

Motivo :

scanfaccetta un puntatore ( int *) nell'argomento, qui 0viene passato (puntatore NULL)

fix:

scanf("%d",&n);

http://ideone.com/MbQhMM


1
Non è esattamente così. scanfè una funzione variadica e int, quando previsto, è stato fornito un argomento di tipo errato ( ) int *. Poiché C (lo so, i compilatori possono rilevare questo in caso di scanf) non possono controllare i tipi in funzione variadica, questo si compila felicemente. 0è infatti letterale puntatore nullo, ma questo vale solo per il letterale usato direttamente, senza memorizzarlo in una variabile. nmai contenuto un puntatore nullo.
Konrad Borowski

È inoltre necessario controllare il valore restituito da scanf per correggere questa funzione.
David Grayson,

1
@David Non è corretto senza controllare il valore restituito, ma non si arresta in modo anomalo o invoca UB - n rimarrà a 0. (Per vedere ciò, prova a reindirizzare un file vuoto come stdin.)
Riking

14

PHP

Questo mi ha morso alcune volte.

<?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();

Risultato atteso:

Hello, World!

Risultato attuale:

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

alias NullPointerException

Motivo:

Per impostazione predefinita, la sintassi del costruttore è retrocompatibile con PHP 4.x e, come tale, la funzione fooè un costruttore valido per la classe Fooe quindi sostituisce il costruttore vuoto predefinito. Questo tipo di errore può essere evitato aggiungendo uno spazio dei nomi al progetto.


9

CoffeeScript (su Node.js)

In CoffeeScript, ?è un operatore esistenziale. Se la variabile esiste, viene utilizzata, altrimenti viene utilizzato il lato destro. Sappiamo tutti che è difficile scrivere programmi portatili. Caso in questione, la stampa in JavaScript è sotto specificata. Browser usano alert(o document.write), Spidermonkey usi guscio print, ed usi Node.JS console.log. Questo è pazzesco, ma CoffeeScript aiuta a risolvere questo problema.

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

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

Eseguiamo questo in Node.js. Dopotutto, vogliamo assicurarci che il nostro script funzioni.

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, perché dovresti lamentarti di questo, quando alertanche non è definito?

Per qualche motivo, in CoffeeScript, ?è associativa sinistra, il che significa che ignora le variabili indefinite solo per il lato sinistro. Non è stato risolto, perché a quanto pare alcuni sviluppatori potrebbero dipendere? essere associativo .


8

Rubino

Trova awk nel PERCORSO di un clone 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')

Oops!

$ 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>'

Dall'errore, sappiamo che p.findchiamato nil.find, così pdeve essere nil. Come è successo?

In Ruby, defha il suo ambito per le variabili locali e non prende mai le variabili locali dall'ambito esterno. Pertanto, l'assegnazione p = ENV['PATH'].split ':'non rientra nell'ambito.

Una variabile non definita di solito causa NameError, ma pè un caso speciale. Ruby ha un metodo globale chiamato p. Quindi p.find { ... }diventa una chiamata di metodo, come p().find { ... }. Quando pnon ha argomenti, ritorna nil. (I golfisti di codice usano pcome scorciatoia per nil.) Quindi nil.find { ... }aumenta NoMethodError.

Lo aggiusto riscrivendo il programma in 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')))

Funziona!

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

Probabilmente lo voglio stampare awk is /usr/bin/awk, ma questo è un bug diverso.


7

C #

With()è un metodo di estensione per l' stringoggetto, che è essenzialmente solo un alias per 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;
        }
    }
}

Sembra buono, vero? Sbagliato.

Type.GetType()richiede un nome di tipo con distinzione tra maiuscole e minuscole. Il problema è che System.stringnon esiste; stringè solo un alias per il vero e proprio tipo: System.String. Sembra che dovrebbe funzionare, ma str.GetMethod()genererà l'eccezione perché str == null.

La maggior parte delle persone che conoscono un po 'le specificità interne della lingua saranno probabilmente in grado di individuare il problema abbastanza rapidamente, ma è comunque qualcosa che è facilmente sfuggito a colpo d'occhio.


È fantastico: D
Knerd,

Questo è un po 'troppo ovvio. Il lettore ottiene un indizio immediato perché ci sono due modi per fare la stessa cosa.GetType() e typeof()e determinando in tal modo un guasto quando il risultato non è lo stesso. Penso che il poster volesse un codice a prova di proiettile che non lo fosse.
Ja72,

Perché la riflessione dovrebbe essere utilizzata in questo metodo di estensione? Il codice ovvio è return string.Format(format, args);. Se fosse necessaria la riflessione (in un altro caso d'uso), si userebbe typeof(string)(rileva errori di involucro in fase di compilazione, nessuna stringa magica), non il GetTypemetodo statico . Quindi l'esempio sembra "irrealistico".
Jeppe Stig Nielsen,

4

Unity3D

public GameObject asset;

Quindi ti dimentichi di trascinare e rilasciare la risorsa lì e BOOM, Unity esplode. Succede tutte le volte.


3
aspetta, lo sviluppo in unity3d richiede un mouse?
Einacio,

1
@Einacio se vuoi le cose più facilmente, puoi semplicemente trascinare una risorsa su una variabile. Molto maneggevole. Ma puoi codificare senza quello se vuoi.
Fabricio

4

Rubino

Solo un semplice codice per prendere il prodotto di un array.

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

Due cose qui. Uno è che l' ..operatore di gamma è inclusivo . Quindi 0..xha x + 1 elementi e include x. Ciò significa che superiamo i limiti dell'array. L'altra cosa è che quando lo fai in Ruby, ti dà solo una nilschiena. È molto divertente quando, ad esempio, il tuo programma lancia exceptdieci righe dopo il bug.


Questo è un po 'troppo ovvio. È come fare for (int i = 0; i <= arr.length; i++)in Java.
Cole Johnson,

3

androide

Vedo che succede troppo spesso. Una persona passa un messaggio all'attività successiva (forse un codice di stato, i dati della mappa, qualunque cosa) e finisce per estrarre un valore nullo da Intent.

A prima vista sembra abbastanza ragionevole. Appena:

  • assicurati che il messaggio non sia nullo
  • imballalo nell'intento
  • iniziare una nuova attività
  • intento in nuove attività
  • estrarre il messaggio per tag

In 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);
}

In 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, il JavaDoc fa dire che Intent.getStringExtra(String)può tornare null, ma solo se il tag non è stato trovato. Chiaramente sto usando lo stesso tag, quindi deve essere qualcos'altro ...


2
Il problema con questa risposta è che le persone che non hanno familiarità con lo sviluppo di Android (come me) non saranno in grado di apprezzarlo.
John Dvorak,

@JanDvorak D'accordo, ma non è un grosso problema. Ci sono altre risposte sul sito che non apprezzo del tutto. :)
Geobits

3

C

Solo una rapida lettura dal buffer, in cui il programmatore è stato anche abbastanza gentile da documentare il potenziale pericolo!

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);
}

Trucco molto semplice e ovvio se ti aspetti un codice dannoso. Il commento che termina '\' sfugge alla nuova riga, quindi al buffer non viene mai allocata memoria. Quindi fallirà lo scanf, poiché il buffer sarà NULL (poiché le variabili dell'ambito del file sono zero inizializzate in C).


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)

Quello che sta succedendo qui è un po 'complicato. In Nimrod, le procedure sono inizializzate di default su nil. Nella prima make_funcchiamata, ci riesce. Il secondo, tuttavia, genera un'eccezione e lascia f2non inizializzato. Viene quindi chiamato, causando un errore.


0

C #

Questo è classico Il metodo molto utileFindStringRepresentationOf creerà una nuova istanza del parametro type specificato, quindi troverà la rappresentazione di stringa di tale istanza.

Sicuramente sarà inutile verificare nullsubito dopo aver creato una nuova istanza, quindi non l'ho fatto ...

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();
}

La modifica objectnella dichiarazione della variabile locale in TNewable o in var(C # 3 e successive) risolve il problema. Suggerimento: il pugilato di Nullable<T>(aka T?) è anomalo in .NET Framework.

Dopo aver risolto il problema come descritto nel testo invisibile sopra, prova anche goodInstance.GetType()(la differenza è che GetType(), a differenza ToString(), non è virtuale, quindi non potevano overridefarlo nel tipo Nullable<>).


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;
}

Quello che ti aspetteresti è "Hello World!" da stampare. Ma vedrai solo immondizia sullo schermo.

Qui a1 viene distrutto al termine dell'ambito init (). Quindi a_ptr, come indica a1, produrrà immondizia.


0

C

#define ONE 0 + 1

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

Spiegazione

Il preprocessore in realtà non calcola 0 + 1, quindi ONEviene letteralmente definito come 0 + 1, risultante 1 / 0 + 1, che è una divisione per zero e genera un'eccezione in virgola mobile.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.