引发空指针异常


14

您的任务是生成一个空指针异常。也就是说,您的程序必须接受一个预期为非空的值,并由于该值为空而引发异常/错误或崩溃。

此外,从读取代码中不能明显看出该值为null。您的目标是使读者清楚地知道该值不为null,即使实际上是非null。

  • 除了null之外,您还可以使用nil,none,nothing或您的语言中的任何等效项。您还可以使用未定义,未初始化等。
  • 您的代码的问题必须是该程序期望一个非null的变量(令人惊讶地)为null。
  • 您的程序可以通过引发异常,引发错误,崩溃或在遇到意外的null时通常执行的操作来响应null。

这是一次人气竞赛,请多加注意!


@Ourous您能举一个例子说明您的意思吗?
Ypnypn

仔细查看之后,它比您要查找的更多是转换错误。
很好2014年

我可以使用编译器错误吗?
2014年

1
@Mark这是一次人气竞赛;让社区决定。我绝对会投票给编译器错误。
1684年

Answers:


33

爪哇

让我们计算一个数字的绝对值。Java为此具有Math.abs,但是我们使用的数字有时可能为null。因此,我们需要一个辅助方法来处理这种情况:

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

如果x为null,则返回null,否则使用Math.abs()。
代码非常简单明了,应该可以正常工作...对吗?

顺便说一句,改用这行:

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

正常工作。我考虑过要把它弄糟,但是..我认为这同样令人困惑:)

好的,一个解释:

首先,Math.abs不带一个整数,而是一个整数(其他数字类型也有重载方法),并且还返回一个整数。在java中,int是原始类型(不能为null),Integer是其对应的类(可以为null)。从Java版本5开始,在需要时会自动执行int和Integer之间的转换。因此Math.abs可以取x,自动转换为int,然后返回int。

现在很奇怪的部分:当三元运算符(?:)必须处理2个表达式,其中一个具有原始类型,另一个具有其对应的类(例如int和Integer)时,一个人会希望java可以转换原始类型类(又称“装箱”),尤其是当它需要的类型(此处是从方法返回)是引用(类)类型,并且第一个表达式也是引用类型时。但是java却恰好相反:它将引用类型转换为原始类型(也称为“拆箱”)。因此,在我们的情况下,它将尝试将x转换为int,但int不能为null,因此将引发NPE。

如果使用Eclipse编译此代码,则实际上会收到警告:“空指针访问:此类型为Integer的表达式为null,但需要自动拆箱”


/programming/7811608/java-npe-in​​-ternary-operator-with-autoboxing
/programming/12763983/nullpointerexception-through-auto-boxing-behavior-of-java三元运算符



当X!= null时会发生什么?(我想它了。)
11684

@ 11684,当x不为null时,它工作正常
由于SE为EVIL,2014年

那我想我不知道这是怎么回事。如果在x不为null的情况下可以正常工作,并且我对它为什么会抛出错误是正确的,则必须从两种意义上分析冒号(我不会在“语言规范”中给出)。
1684年

@ 11684不确定您对“两种感觉”的含义,但是无论如何我现在都添加了一个解释
aidtsu退出了,因为SE是邪恶的2014年

23

C

每个C程序员都至少一次犯了此错误。

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

原因:

scanfint *在参数中使用一个指针(),在此处0传递(NULL指针)

固定:

scanf("%d",&n);

http://ideone.com/MbQhMM


1
不是那样的 scanf是可变参数函数,并且int在预期时给出了错误类型()的参数int *。因为C(我知道编译器可以在的情况下检测到这一点scanf)无法检查可变参数函数中的类型,所以这很容易编译。0确实是空指针文字,但这仅适用于直接使用的文字本身,而不存储在变量中。n从不包含空指针。
Konrad Borowski14年

您还需要检查scanf的返回值以修复此功能。
大卫·格雷森

1
@David如果不检查返回值是不正确的,但是它不会崩溃或调用UB-n将保持为0。(要看到这种情况,请尝试将空文件重定向为stdin。)
2014年

14

的PHP

这咬了我几次。

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

预期结果:

Hello, World!

实际结果:

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

又名NullPointerException

原因:

默认情况下,构造函数语法与PHP 4.x向后兼容,因此,该函数foo是该类的有效构造函数Foo,因此将覆盖默认的空构造函数。通过将命名空间添加到项目中,可以避免此类错误。


9

CoffeeScript(在Node.js上)

在CoffeeScript中,?是存在运算符。如果存在该变量,则使用该变量,否则使用右侧。众所周知,编写可移植程序很困难。例如,未指定使用JavaScript进行打印。浏览器使用alert(或document.write),SpiderMonkey shell使用print,Node.js使用console.log。这太疯狂了,但是CoffeeScript可以解决这个问题。

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

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

让我们在Node.js下运行它。毕竟,我们要确保脚本能够正常工作。

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)

嗯,为什么alert还没有定义呢?

由于某些原因,在CoffeeScript中,它?是左关联的,这意味着它仅在左侧忽略未定义的变量。它是未固定的,因为显然某些开发人员可能依赖于?保持联想


8

红宝石

在Unix克隆的PATH中找到awk。

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

糟糕!

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

从错误中我们知道p.find称为nil.find,因此p必须nil。这怎么发生的?

在Ruby中,def它具有局部变量的作用域,并且永远不会从外部范围获取局部变量。因此,分配p = ENV['PATH'].split ':'不在范围内。

未定义的变量通常会导致NameError,但这p是特例。Ruby有一个名为的全局方法p。因此p.find { ... }成为一个方法调用,如p().find { ... }。如果p没有参数,则返回nil。(高尔夫球手将代码p用作的快捷方式nil。)然后nil.find { ... }加注NoMethodError

我通过用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')))

有用!

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

我可能希望将其打印出来awk is /usr/bin/awk,但这是一个不同的错误。


7

C#

With()string对象的扩展方法,本质上只是的别名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;
        }
    }
}

看起来不错吧?错误。

Type.GetType()需要一个全限定的,区分大小写的类型名称。问题是System.string不存在。string只是实际类型的别名:System.String。看起来应该可以,但是str.GetMethod()会因为抛出异常str == null

大多数对语言的内部细节有所了解的人可能很快就能发现问题,但是仍然很容易一眼就忽略掉它。


:D
Knerd

这有点太明显了。读者立即得到一个线索,因为有两种方法可以做同样的事情.GetType(),并typeof()因而导致故障的时候,结果是不一样的。我认为发布者想要的不是项目符号证明代码。
ja72 2014年

为什么在此扩展方法中使用反射?明显的代码是return string.Format(format, args);。如果需要反射(在另一种用例中),则将使用typeof(string)(在编译时捕获大小写错误,不需要魔术字符串),而不是静态GetType方法。因此该示例似乎“不切实际”。
Jeppe Stig Nielsen

4

Unity3D

public GameObject asset;

然后,您忘记将资产拖放到那里,BOOM就会爆炸。一直发生。


3
等待,在unity3d中开发需要鼠标吗?
艾纳西奥(Einacio)2014年

1
@Einacio如果您希望事情变得更轻松,则可以将资产拖放到变量中。非常便利。但是,如果您愿意的话,也可以不编写该代码。
Fabricio 2014年

4

红宝石

只需一些简单的代码即可获得数组的乘积。

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

这里有两件事。一是..范围运算符包含在内。因此0..xx + 1个元素并包含x。这意味着我们超出了数组的界限。另一件事是,当您在Ruby中执行此操作时,它只会给您带来nil回报。例如,当您的程序在错误抛出except十行时,这会很有趣。


这有点太明显了。就像for (int i = 0; i <= arr.length; i++)用Java 做一样。
科尔·约翰逊

3

安卓系统

我看到这种情况经常发生。一个人将消息传递到下一个活动(可能是状态码,地图数据等),最后从Intent

乍看之下似乎很合理。只是:

  • 确保消息不为空
  • 打包成意图
  • 开始新活动
  • 致力于新活动
  • 按标签提取消息

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

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 确实Intent.getStringExtra(String)可能会返回null,但前提是没有找到标签。显然,我使用的是同一标签,因此它必须是其他标签...


2
这个答案的问题是,不熟悉Android开发的人(例如我)将无法欣赏它。
约翰·德沃夏克

@JanDvorak同意,但没什么大不了的。该网站上还有其他答案,我也不太欣赏。:)
Geobits

3

C

只需从缓冲区中快速读取一下,程序员就已经足够友好地记录了潜在的危险!

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

如果您期望使用恶意代码,这是非常简单且显而易见的技巧。以'\'结尾的注释转义换行符,因此永远不会为缓冲区分配内存。然后,它将使scanf失败,因为缓冲区将为NULL(因为文件范围变量在C中初始化为零)。


0

尼姆罗德

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)

这里发生的事情有点复杂。在Nimrod中,过程默认初始化为nil。在第一个make_func呼叫中,它成功。但是,第二个引发异常,并且f2未初始化。然后调用它,导致错误。


0

C#

这是经典的。该非常有用的方法FindStringRepresentationOf将在指定的类型参数的新实例,然后找到该实例的字符串表示。

null创建新实例后立即检查肯定会很浪费,所以我还没有这样做……

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

object局部变量声明更改TNewablevar(或更改为C#3及更高版本)会使问题消失。提示:Nullable<T>(aka T?)的 装箱在.NET Framework中是异常的。

修复了上面无形文本中描述的问题后,请尝试goodInstance.GetType()(区别在于GetType(),与不同ToString(),它是非虚拟的,因此他们不能override在类型中使用它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;
}

您所期望的是“ Hello World!” 待打印。但是您只会在屏幕上看到垃圾。

当范围init()完成时,a1被销毁。因此,指向a1的a_ptr将产生垃圾。


0

C

#define ONE 0 + 1

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

说明

预处理器实际上并不计算0 + 1,因此按ONE字面意义定义为0 + 1,导致,导致1 / 0 + 1,它被零除,并导致浮点异常。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.