Lanzar una excepción de puntero nulo [cerrado]


14

Su tarea es generar una excepción de puntero nulo. Es decir, su programa debe aceptar un valor que espera que no sea nulo, y lanzar una excepción / error o bloqueo porque el valor es nulo.

Además, no puede ser obvio al leer el código que el valor es nulo. Su objetivo es hacer que el lector tenga claro que el valor no es nulo, aunque en realidad lo sea.

  • En lugar de nulo, puede usar nil, none, nothing o lo que sea equivalente en su idioma. También puede usar indefinido, sin inicializar, etc.
  • El problema con su código debe ser que la variable es (sorprendentemente) nula donde el programa espera una variable no nula.
  • Su programa puede responder al nulo lanzando una excepción, lanzando un error, fallando o lo que sea que haga normalmente cuando se encuentra un nulo inesperado.

Este es un concurso de popularidad, ¡así que sé inteligente!


@Ourous ¿Puedes dar un ejemplo para mostrar lo que quieres decir?
Ypnypn

Después de mirarlo, es más un error de lanzamiento que lo que estás buscando.
Precioso

¿Puedo usar un error del compilador?
Mark

1
@ Mark Es un concurso de popularidad; deja que la comunidad decida. Definitivamente votaría por un error del compilador.
11684

Respuestas:


33

Java

Calculemos el valor absoluto de un número. Java tiene Math.abs para este propósito, sin embargo, el número que estamos usando puede ser nulo a veces. Por lo tanto, necesitamos un método auxiliar para lidiar con ese 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));
    }
}

Si x es nulo, devuelve nulo, de lo contrario use Math.abs ().
El código es muy simple y claro, y debería funcionar bien ... ¿verdad?

Por cierto, usando esta línea en su lugar:

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

funciona correctamente Pensé en hacer de esto un spoiler, pero ... creo que es igual de desconcertante :)

Ok, una explicación:

Primero, Math.abs no toma un entero, sino un int (también hay métodos sobrecargados para otros tipos numéricos) y también devuelve un int. En java, int es un tipo primitivo (y no puede ser nulo) y Integer es su clase correspondiente (y puede ser nulo). Desde la versión 5 de Java, una conversión entre int e Integer se realiza automáticamente cuando es necesario. De modo que Math.abs puede tomar x, convertido automáticamente a int, y devolver un int.

Ahora la parte extraña: cuando el operador ternario (? :) tiene que lidiar con 2 expresiones donde una tiene un tipo primitivo y la otra tiene su clase correspondiente (como int e Integer), uno esperaría que Java convirtiera la primitiva a la clase (también conocido como "boxeo"), especialmente cuando el tipo que necesita (aquí para regresar del método) es el tipo de referencia (clase), y la primera expresión también es del tipo de referencia. Pero Java hace exactamente lo contrario: convierte el tipo de referencia al tipo primitivo (también conocido como "unboxing"). Entonces, en nuestro caso, intentaría convertir x a int, pero int no puede ser nulo, por lo tanto arroja un NPE.

Si compila este código usando Eclipse, en realidad recibirá una advertencia: "Acceso de puntero nulo: esta expresión de tipo Integer es nula pero requiere unboxing automático"


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



SPOILERS ¿Qué sucede cuando x! = Nulo? (Ya lo descubrí.)
11684

@ 11684 cuando x no es nulo, funciona bien
Aditsu se retiró porque SE es MALO

Entonces creo que no sé cómo funciona esto. Si funciona cuando x no es nulo y tengo razón acerca de por qué esto arroja, el colon debe analizarse en dos sentidos (que no pondría en una Especificación de idioma).
11684

@ 11684 no estoy seguro de lo que quieres decir con "dos sentidos", pero de todos modos agregué una explicación ahora
aditsu se retiró porque SE es MAL

23

C

Todos los programadores de C cometieron este error, al menos una 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ón:

scanftoma un puntero ( int *) en argumento, aquí 0se pasa (puntero NULO)

Reparar:

scanf("%d",&n);

http://ideone.com/MbQhMM


1
No es exactamente así. scanfes una función variada, y se dio un argumento de tipo incorrecto ( int), cuando se esperaba int *. Debido a que C (lo sé, los compiladores pueden detectar esto en el caso de scanf) no puede verificar los tipos en función variadic, esto compila felizmente. 0es, de hecho, literal de puntero nulo, pero esto solo se aplica al literal utilizado directamente, sin almacenarlo en una variable. nnunca contuvo un puntero nulo.
Konrad Borowski

También debe verificar el valor de retorno de scanf para corregir esta función.
David Grayson

1
@ David No es correcto sin comprobar el valor de retorno, pero no se bloqueará o invocar UB - (. Para ver que esto suceda, intente redirigir un archivo vacío como entrada estándar) n permanecerá a 0.
Riking

14

PHP

Este me ha mordido algunas veces.

<?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 actual:

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

aka NullPointerException

Razón:

Por defecto, la sintaxis del constructor es compatible con PHP 4.x, y como tal, la función fooes un constructor válido para la clase Fooy, por lo tanto, anula el constructor vacío predeterminado. Este tipo de error se puede evitar agregando un espacio de nombres a su proyecto.


9

CoffeeScript (en Node.js)

En CoffeeScript, ?es operador existencial. Si la variable existe, se usa, de lo contrario se usa el lado derecho. Todos sabemos que es difícil escribir programas portátiles. Caso en cuestión, la impresión en JavaScript está debajo de lo especificado. Los navegadores utilizan alert(o document.write), SpiderMonkey usos de la cáscara print, y los usos Node.js console.log. Esto es una locura, pero CoffeeScript ayuda con este problema.

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

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

Ejecutemos esto en Node.js. Después de todo, queremos asegurarnos de que nuestro 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 qué te quejarías de eso, cuando alerttampoco está definido?

Por alguna razón, en CoffeeScript, ?se deja asociativo, lo que significa que ignora las variables indefinidas solo para el lado izquierdo. No está arreglado, porque aparentemente algunos desarrolladores pueden depender de? quedando asociativo .


8

Rubí

Encuentra awk en la RUTA de un clon de 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')

¡Uy!

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

Por el error, sabemos que p.findllamó nil.find, así pdebe ser nil. ¿Cómo pasó esto?

En Ruby, deftiene su propio alcance para variables locales, y nunca toma variables locales del alcance externo. Por lo tanto, la asignación p = ENV['PATH'].split ':'no está dentro del alcance.

Una variable indefinida generalmente causa NameError, pero pes un caso especial. Ruby tiene un método global llamado p. Entonces se p.find { ... }convierte en una llamada al método, como p().find { ... }. Cuando pno tiene argumentos, vuelve nil. (Los golfistas de código usan pcomo atajo para nil). Luego nil.find { ... }aumenta NoMethodError.

Lo arreglo reescribiendo el programa en 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

Probablemente quiera que se imprima awk is /usr/bin/awk, pero ese es un error diferente.


7

C#

With()es un método de extensión para el stringobjeto, que es esencialmente solo un 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;
        }
    }
}

Se ve bien, ¿verdad? Incorrecto.

Type.GetType()requiere un nombre de tipo totalmente calificado y sensible a mayúsculas y minúsculas. El problema es que System.stringno existe; stringes sólo un alias para el actual tipo: System.String. Parece que debería funcionar, pero str.GetMethod()arrojará la excepción porque str == null.

La mayoría de las personas que conocen un poco los detalles internos del lenguaje probablemente podrán detectar el problema con bastante rapidez, pero aún así es algo que fácilmente se pasa por alto de un vistazo.


Esa es genial: D
Knerd

Esto es un poco demasiado obvio. El lector tener una idea inmediata porque hay dos maneras de hacer lo mismo .GetType()y typeof()y lo que conduce a un fallo cuando el resultado no es el mismo. Creo que el cartel quería un código a prueba de balas que no lo es.
ja72

¿Por qué se usaría la reflexión en este método de extensión? El código obvio es return string.Format(format, args);. Si se necesitara reflexión (en otro caso de uso), se usaría typeof(string)(atrapa errores de carcasa en tiempo de compilación, sin cadena mágica), no el GetTypemétodo estático . Entonces el ejemplo parece "poco realista".
Jeppe Stig Nielsen

4

Unity3D

public GameObject asset;

Luego se olvida de arrastrar y soltar el activo allí y BOOM, Unity explota. Pasa todo el tiempo.


3
espera, el desarrollo en unity3d requiere un mouse?
Einacio

1
@Einacio si quieres que las cosas sean más fáciles, puedes arrastrar y soltar un activo a una variable. Muy útil. Pero puede codificar sin eso si lo desea.
Fabricio

4

Rubí

Solo un código simple para tomar el producto de una 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

Dos cosas aquí. Una es que el ..operador de rango es inclusivo . Entonces 0..xtiene x + 1 elementos e incluye x. Esto significa que superamos los límites de la matriz. La otra cosa es que cuando haces esto en Ruby, solo te nildevuelve la espalda. Esto es muy divertido cuando, por ejemplo, su programa arroja exceptdiez líneas después del error.


Esto es demasiado obvio. Es como hacerlo for (int i = 0; i <= arr.length; i++)en Java.
Cole Johnson

3

Androide

Veo que esto sucede con demasiada frecuencia. Una persona pasa un mensaje a la siguiente actividad (tal vez un código de estado, datos de mapas, lo que sea), y termina sacando un valor nulo del Intent.

A primera vista parece bastante razonable. Sólo:

  • asegúrese de que el mensaje no sea nulo
  • empacarlo en la intención
  • comenzar una nueva actividad
  • obtener intención en una nueva actividad
  • extraer mensaje por etiqueta

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

En 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, el JavaDoc hace decir que Intent.getStringExtra(String)puede volver null, pero sólo si no se ha encontrado la etiqueta. Claramente estoy usando la misma etiqueta, por lo que debe ser otra cosa ...


2
El problema con esta respuesta es que las personas que no están familiarizadas con el desarrollo de Android (como yo) no podrán apreciarlo.
John Dvorak

@ JanDvorak De acuerdo, pero no es gran cosa. Hay otras respuestas en el sitio que no aprecio totalmente también. :)
Geobits

3

C

¡Solo una lectura rápida del búfer, donde el programador incluso ha tenido la amabilidad de documentar el peligro 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);
}

Truco muy simple y obvio si espera código malicioso. El comentario que termina '\' escapa a la nueva línea, por lo que el búfer nunca tiene memoria asignada. Entonces fallará el scanf, ya que el búfer será NULO (ya que las variables del alcance del archivo son cero inicializadas en 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)

Lo que sucede aquí es un poco complicado. En Nimrod, los procedimientos están inicializados por defecto nil. En la primera make_funcllamada, tiene éxito. El segundo, sin embargo, arroja una excepción y se deja sin f2inicializar. Luego se llama, causando un error.


0

C#

Esto es clasico. El método muy útil FindStringRepresentationOfcreará una nueva instancia del parámetro de tipo especificado, luego encontrará la representación de cadena de esa instancia.

Seguramente será un desperdicio verificarlo nullinmediatamente después de haber creado una nueva instancia, por lo que no lo he hecho ...

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

Cambiar objecten la declaración de variable local en TNewable o dentro var(C # 3 y posterior) hace que el problema desaparezca. Sugerencia: el boxeo de Nullable<T>(aka T?) es anómalo en .NET Framework.

Después de haber solucionado el problema como se describe en el texto invisible anterior, intente también goodInstance.GetType()(la diferencia es que GetType(), a diferencia ToString(), no es virtual, por lo que no pudieron overridehacerlo en el 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;
}

Lo que esperarías es "¡Hola Mundo!" para ser impreso Pero solo verá basura en la pantalla.

Aquí el a1 se destruye cuando finaliza el ámbito init (). Entonces a_ptr, como apunta a a1, producirá basura.


0

C

#define ONE 0 + 1

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

Explicación

El preprocesador en realidad no calcula 0 + 1, por lo que ONEse define literalmente como 0 + 1, lo 1 / 0 + 1que resulta en una división por cero y da como resultado una excepción de coma flotante.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.