Zgłoszenie wyjątku pustego wskaźnika [zamknięte]


14

Twoim zadaniem jest wygenerowanie wyjątku pustego wskaźnika. Oznacza to, że Twój program musi zaakceptować wartość, która według niego nie jest zerowa, i zgłosić wyjątek / błąd lub awarię, ponieważ wartość jest pusta.

Co więcej, odczytanie kodu nie może być oczywiste, że wartość jest zerowa. Twoim celem jest wyjaśnienie czytelnikowi, że wartość nie jest równa zeru, nawet jeśli tak naprawdę jest.

  • Zamiast wartości zerowej możesz użyć wartości zero, brak, nic lub cokolwiek równoważnego w twoim języku. Możesz także użyć niezdefiniowanej, niezainicjowanej itd.
  • Problem z twoim kodem musi polegać na tym, że zmienna ma (zaskakująco) null, gdy program oczekuje zmiennej innej niż null.
  • Twój program może odpowiedzieć na wartość zerową, zgłaszając wyjątek, zgłaszając błąd, zawieszając się lub wykonując inne czynności, gdy napotka nieoczekiwany wynik zerowy.

To konkurs popularności, więc bądź sprytny!


@Ourous Czy możesz podać przykład pokazujący, co masz na myśli?
Ypnypn

Po przejrzeniu go jest to bardziej błąd przesyłania niż to, czego szukasz.
Οurous

Czy mogę używać błędu kompilatora?
Mark

1
@Mark To konkurs popularności; niech społeczność zdecyduje. Zdecydowanie głosowałbym na błąd kompilatora.
11684

Odpowiedzi:


33

Jawa

Obliczmy wartość bezwzględną liczby. Java ma Math.abs do tego celu, jednak liczba, której używamy, może czasami być zerowa. Potrzebujemy więc metody pomocniczej, aby poradzić sobie z tą sprawą:

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

Jeśli x ma wartość null, zwróć null, w przeciwnym razie użyj Math.abs ().
Kod jest bardzo prosty i przejrzysty i powinien działać dobrze ... prawda?

Nawiasem mówiąc, zamiast tego użyj tej linii:

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

działa poprawnie. Myślałem o zrobieniu tego spoilera, ale .. Myślę, że to równie zagadkowe :)

Ok wyjaśnienie:

Po pierwsze, Math.abs nie przyjmuje liczby całkowitej, ale liczbę całkowitą (istnieją również przeciążone metody dla innych typów liczbowych), a także zwraca liczbę całkowitą. W Javie int jest typem pierwotnym (i nie może mieć wartości null), a liczba całkowita jest odpowiednią klasą (i może mieć wartość null). Od wersji 5 Java, konwersja między int i Integer jest wykonywana automatycznie w razie potrzeby. Math.abs może więc pobrać x, automatycznie przekonwertować na int i zwrócić int.

Teraz dziwna część: gdy operator trójskładnikowy (? :) ma do czynienia z 2 wyrażeniami, w których jedno ma typ pierwotny, a drugie ma odpowiednią klasę (taką jak int i Integer), można się spodziewać, że Java przekształci operację pierwotną do klasy (inaczej „boks”), szczególnie gdy typ, którego potrzebuje (tutaj do powrotu z metody), jest typem referencyjnym (klasowym), a pierwsze wyrażenie jest również typem referencyjnym. Ale Java robi dokładnie odwrotnie: konwertuje typ referencyjny na typ pierwotny (aka „unboxing”). Więc w naszym przypadku próbowałby przekonwertować x na int, ale int nie może mieć wartości null, więc generuje NPE.

Jeśli skompilujesz ten kod za pomocą Eclipse, faktycznie pojawi się ostrzeżenie: „Dostęp do wskaźnika zerowego: To wyrażenie typu Integer ma wartość NULL, ale wymaga automatycznego rozpakowania”


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



SPOILERY Co się stanie, gdy x! = Null? (Już to rozgryzłem)
11684

@ 11684, gdy x nie jest zerowe, działa dobrze
aditsu kończy, ponieważ SE ma wartość EVIL

Więc myślę, że nie wiem, jak to działa. Jeśli to działa, gdy x nie jest zerowe i mam rację co do tego, dlaczego to wyrzuca, dwukropek musi zostać przeanalizowany na dwa sposoby (czego nie umieszczałbym w specyfikacji języka).
11684

@ 11684 nie jestem pewien, co masz na myśli przez „dwa zmysły”, ale w każdym razie dodałem teraz wyjaśnienie
aditsu zrezygnowało, ponieważ SE to EVIL

23

do

Każdy programista C popełnił ten błąd przynajmniej raz.

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

Powód:

scanfprzyjmuje int *argument ( ) w argumencie, tutaj 0jest przekazywany (wskaźnik NULL)

Naprawić:

scanf("%d",&n);

http://ideone.com/MbQhMM


1
To nie jest dokładnie tak. scanfjest funkcją wariadyczną i intpodano argument niepoprawnego typu ( ), gdy się tego spodziewał int *. Ponieważ C (wiem, kompilatory potrafią to wykryć w przypadku scanf) nie może sprawdzić typów w funkcji variadic, to na szczęście się kompiluje. 0jest rzeczywiście literałem zerowym, ale dotyczy to samego bezpośrednio używanego literału, bez przechowywania go w zmiennej. nnigdy nie zawierał wskaźnika zerowego.
Konrad Borowski

Musisz również sprawdzić wartość zwracaną przez scanf, aby naprawić tę funkcję.
David Grayson

1
@ David Nie jest poprawne bez sprawdzenia wartości zwracanej, ale nie zawiesza się ani nie wywołuje UB - n pozostanie na 0. (Aby to zobaczyć, spróbuj przekierować pusty plik jako stdin.)
Riking

14

PHP

Ten ugryzł mnie kilka razy.

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

Spodziewany wynik:

Hello, World!

Aktualny rezultat:

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

alias NullPointerException

Powód:

Domyślnie składnia konstruktora jest wstecznie kompatybilna z PHP 4.x i jako taka funkcja foojest poprawnym konstruktorem dla klasy Foo, a zatem zastępuje domyślny pusty konstruktor. Tego rodzaju błędów można uniknąć, dodając przestrzeń nazw do projektu.


9

CoffeeScript (w Node.js)

W CoffeeScript ?jest operatorem egzystencjalnym. Jeśli zmienna istnieje, jest używana, w przeciwnym razie używana jest prawa strona. Wszyscy wiemy, że trudno pisać programy przenośne. W tym przypadku drukowanie w JavaScript jest niedokładne. Przeglądarka używa alert(lub document.write) zastosowania powłoki spidermonkey printi zastosowania node.js console.log. To szalone, ale CoffeeScript pomaga rozwiązać ten problem.

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

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

Uruchommy to pod Node.js. W końcu chcemy się upewnić, że nasz skrypt działa.

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, dlaczego miałbyś na to narzekać, skoro alertrównież nie jest zdefiniowany?

Z jakiegoś powodu w CoffeeScript ?pozostaje asocjacyjny, co oznacza, że ​​ignoruje niezdefiniowane zmienne tylko dla lewej strony. Nie jest naprawiony, ponieważ najwyraźniej niektórzy programiści mogą polegać? pozostać asocjatywnym .


8

Rubin

Znajdź awk w ŚCIEŻCE klonu Uniksa.

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

Ups!

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

Z błędu wiemy, że to p.findwywołanie nil.find, tak też pmusi być nil. Jak to się stało?

W Ruby defma własny zakres zmiennych lokalnych i nigdy nie pobiera zmiennych lokalnych z zakresu zewnętrznego. Zatem zadanie p = ENV['PATH'].split ':'nie jest objęte zakresem.

Nieokreślona zmienna zwykle powoduje NameError, ale pjest szczególnym przypadkiem. Ruby ma globalną metodę o nazwie p. p.find { ... }Staje się więc wywołaniem metody p().find { ... }. Gdy pnie ma argumentów, zwraca nil. (Kod golfistów używa pjako skrótu do nil.) Następnie nil.find { ... }podbija NoMethodError.

Naprawiam to, przepisując program w Pythonie.

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

To działa!

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

Prawdopodobnie chcę to wydrukować awk is /usr/bin/awk, ale to inny błąd.


7

DO#

With()to metoda rozszerzenia stringobiektu, która jest w zasadzie tylko aliasem 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;
        }
    }
}

Wygląda dobrze, prawda? Źle.

Type.GetType()wymaga pełnej nazwy z rozróżnianiem wielkości liter. Problem polega na tym, System.stringże nie istnieje; stringjest po prostu alias dla rzeczywistego typu: System.String. Wygląda na to, że powinno działać, ale str.GetMethod()zwróci wyjątek, ponieważ str == null.

Większość osób, które trochę wiedzą na temat wewnętrznej specyfiki języka, prawdopodobnie będą w stanie dość szybko wykryć problem, ale wciąż jest to coś, czego łatwo nie zauważyć na pierwszy rzut oka.


To jest świetne: D
Knerd

To jest trochę zbyt oczywiste. Czytelnik od razu uzyskać wskazówkę, ponieważ istnieją dwa sposoby, aby zrobić to samo .GetType()i typeof(), a tym samym prowadzi do winy, gdy wynik nie jest taki sam. Myślę, że plakat chciał kodu odpornego na pociski, który nie jest.
ja72

Dlaczego w tej metodzie rozszerzenia zastosowano odbicie? Oczywistym kodem jest return string.Format(format, args);. Gdyby potrzebne było odbicie (w innym przypadku użycia), można by użyć typeof(string)(wyłapuje błędy w obudowie w czasie kompilacji, bez magicznego ciągu), a nie GetTypemetody statycznej . Przykład wydaje się więc „nierealny”.
Jeppe Stig Nielsen

4

Unity3D

public GameObject asset;

Potem zapomnisz przeciągnąć i upuścić tam zasób i BOOM, Unity eksploduje. Dzieje się cały czas.


3
czekaj, rozwój w unity3d wymaga myszy?
Einacio

1
@Einacio, jeśli chcesz łatwiej, możesz po prostu przeciągnąć i upuścić zasób do zmiennej. Bardzo przydatny. Ale możesz kodować bez tego, jeśli chcesz.
Fabricio

4

Rubin

Tylko prosty kod do pobrania iloczynu macierzy.

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

Dwie rzeczy tutaj. Jednym z nich ..jest włączenie operatora zasięgu . 0..xMa więc x + 1 elementów i zawiera x. Oznacza to, że przekraczamy granice tablicy. Drugą rzeczą jest to, że kiedy robisz to w Ruby, daje ci to tylko nilwsparcie. To świetna zabawa, gdy, powiedzmy, twój program rzuca exceptdziesięć błędów po błędzie.


To jest zbyt oczywiste. To jak robienie for (int i = 0; i <= arr.length; i++)w Javie.
Cole Johnson

3

Android

Widzę, że zdarza się to zbyt często. Osoba przekazuje komunikat do następnej czynności (może kod statusu, dane mapy, cokolwiek) i kończy pobieranie wartości null z Intent.

Na pierwszy rzut oka wydaje się to całkiem rozsądne. Właśnie:

  • upewnij się, że wiadomość nie ma wartości NULL
  • zapakuj to w intencję
  • rozpocząć nową działalność
  • skupić się na nowej działalności
  • wyodrębnij wiadomość według tagu

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

W 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, Javadoc nie powiedzieć, że Intent.getStringExtra(String)może wrócić null, ale tylko wtedy, gdy znacznik nie został znaleziony. Oczywiście używam tego samego tagu, więc musi to być coś innego ...


2
Problem z tą odpowiedzią polega na tym, że osoby nieobeznane z programowaniem Androida (takie jak ja) nie będą w stanie tego docenić.
John Dvorak

@JanDvorak Zgoda, ale nic wielkiego. Na stronie są też inne odpowiedzi, których nie do końca doceniam. :)
Geobits

3

do

Wystarczy szybki odczyt z bufora, w którym programista był na tyle miły, że udokumentował potencjalne niebezpieczeństwo!

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

Bardzo prosta i oczywista sztuczka, jeśli oczekujesz złośliwego kodu. Komentarz kończący się na „\” ucieka od nowej linii, więc buforowi nigdy nie jest przydzielana pamięć. Następnie nie powiedzie się skanowanie, ponieważ bufor będzie miał wartość NULL (ponieważ zmienne zakresu plików są zerowane w 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)

To, co się tutaj dzieje, jest trochę skomplikowane. W Nimrod procedury są domyślnie inicjowane nil. W pierwszym make_funcpołączeniu udaje się. Drugi jednak generuje wyjątek i pozostawia f2niezainicjowany. Następnie jest wywoływany, co powoduje błąd.


0

DO#

To jest klasyczne. Bardzo użyteczna metodaFindStringRepresentationOf stworzy nową instancję określonego parametru typu, a następnie znaleźć reprezentację ciąg tej instancji.

Na pewno marnowanie czasu nullpo utworzeniu nowej instancji będzie marnotrawstwem , więc nie zrobiłem tego ...

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

Zmiana objectdeklaracji zmiennej lokalnej na TNewable lub na var(C # 3 i nowsze) powoduje, że problem zniknie. Wskazówka: Boks Nullable<T>(aka T?) jest nieprawidłowy w .NET Framework.

Po rozwiązaniu problemu, jak opisano w niewidzialnym tekście powyżej, spróbuj również goodInstance.GetType()(różnica polega na tym GetType(), że w przeciwieństwie do ToString()nie-wirtualnego, więc nie mogli tego overridezrobić w typie 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;
}

Czego można oczekiwać od „Hello World!” do wydrukowania. Ale na ekranie zobaczysz tylko śmieci.

Tutaj a1 jest niszczony po zakończeniu zakresu init (). Więc a_ptr, jak wskazuje na a1, będzie produkować śmieci.


0

do

#define ONE 0 + 1

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

Wyjaśnienie

Preprocesor tak naprawdę nie oblicza 0 + 1, więc ONEjest dosłownie zdefiniowany jako 0 + 1wynik 1 / 0 + 1, który jest dzieleniem przez zero, i powoduje wyjątek zmiennoprzecinkowy.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.