Бросить исключение нулевого указателя [закрыто]


14

Ваша задача - создать исключение с нулевым указателем. То есть ваша программа должна принять значение, которое, как ожидается, будет ненулевым, и выдать исключение / ошибку или сбой, потому что значение равно нулю.

Кроме того, при чтении кода не может быть очевидным, что значение равно нулю. Ваша цель - показать читателю, что значение не равно нулю, хотя оно и есть на самом деле.

  • Вместо нуля вы можете использовать ноль, ни один, ничего, или любой другой эквивалент в вашем языке. Вы также можете использовать неопределенные, неинициализированные и так далее.
  • Проблема с вашим кодом должна заключаться в том, что переменная (удивительно) равна нулю, когда программа ожидает ненулевую переменную.
  • Ваша программа может реагировать на нулевое значение, выдавая исключение, выбрасывая ошибку, сбой или все, что она обычно делает, когда встречается неожиданное нулевое значение.

Это конкурс популярности, так что будьте умнее!


@Ourous Можете ли вы привести пример, чтобы показать, что вы имеете в виду?
Ипныпн

После просмотра, это скорее ошибка приведения, чем то, что вы ищете.
Οurous

Могу ли я использовать ошибку компилятора?
Марк

1
@Mark - это конкурс популярности; пусть сообщество решит. Я бы определенно проголосовал за ошибку компилятора.
11684

Ответы:


33

Джава

Давайте вычислим абсолютное значение числа. В Java есть Math.abs для этой цели, однако иногда используемое число может быть нулевым. Таким образом, нам нужен вспомогательный метод для решения этого случая:

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 равен нулю, вернуть ноль, иначе используйте Math.abs ().
Код очень прост и понятен, и должен нормально работать ... верно?

Кстати, используя эту строку вместо:

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

работает правильно. Я думал о том, чтобы сделать этот спойлер, но .. я думаю, что это так же озадачивает :)

Хорошо, объяснение:

Во-первых, Math.abs принимает не Integer, а int (есть также перегруженные методы для других числовых типов), а также возвращает int. В java int является примитивным типом (и не может быть нулевым), а Integer является его соответствующим классом (и может быть нулевым). Начиная с версии 5 Java, преобразование между int и Integer выполняется автоматически при необходимости. Таким образом, Math.abs может взять x, автоматически преобразовать его в int и вернуть int.

Теперь странная часть: когда тернарный оператор (? :) должен иметь дело с 2 выражениями, где одно имеет примитивный тип, а другое имеет соответствующий класс (такой как int и Integer), можно ожидать, что java преобразует примитив к классу (он же «бокс»), особенно когда тип, который ему нужен (здесь для возврата из метода), является ссылочным (классом) типом, а первое выражение также имеет ссылочный тип. Но Java делает с точностью до наоборот: он преобразует ссылочный тип в примитивный тип (он же «unboxing»). Так что в нашем случае он попытается преобразовать x в int, но int не может быть нулевым, поэтому он выбрасывает NPE.

Если вы скомпилируете этот код с использованием Eclipse, вы фактически получите предупреждение: «Доступ к нулевому указателю: это выражение типа Integer является нулевым, но требует автоматической распаковки»


/programming/7811608/java-npe-in-ternary-operator-with-autoboxing
/programming/12763983/nullpointerexception-through-auto-boxing-behavior-of-java -ternary-оператор



СПОЙЛЕРЫ Что происходит, когда х! = Ноль? (Я уже понял это.)
11684

@ 11684, когда x не нуль, он работает нормально
адицу ушел, потому что SE ЗЛО

Тогда я думаю, что я не знаю, как это работает. Если это работает, когда x не нуль, и я прав относительно того, почему это выбрасывает, двоеточие должно быть проанализировано в двух смыслах (который я не поместил бы в Спецификацию языка).
11684

@ 11684 не уверен, что ты имеешь в виду под "двумя чувствами", но я все равно добавил объяснение
адицу ушел, потому что SE ЗЛО

23

С

Каждый программист 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;
}

Причина:

scanfпринимает указатель ( int *) в аргументе, здесь 0передается (NULL-указатель)

Fix:

scanf("%d",&n);

http://ideone.com/MbQhMM


1
Это не совсем так. scanfявляется переменной функцией, и аргумент неверного типа ( int) был задан, когда ожидалось int *. Поскольку C (я знаю, компиляторы могут обнаружить это в случае scanf) не может проверять типы в функции с переменным числом, это успешно компилируется. 0действительно является литералом нулевого указателя, но это относится только к непосредственно используемому литералу, не сохраняя его в переменной. nникогда не содержал нулевого указателя.
Конрад Боровски

Вам также необходимо проверить возвращаемое значение scanf, чтобы исправить эту функцию.
Дэвид Грейсон

1
@David Это не правильно без проверки возвращаемого значения, но это не приведет к сбою или вызову UB - n останется на 0. (Чтобы увидеть это, попробуйте перенаправить пустой файл как stdin.)
Riking

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 Оболочка использует 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

Рубин

Найдите awk в ПУТИ клона 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')

К сожалению!

$ 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. Как это случилось?

В рубине 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

Почему отражение будет использоваться в этом методе расширения? Очевидный код есть return string.Format(format, args);. Если бы понадобилось отражение (в другом случае использования), он использовал бы typeof(string)(ловит ошибки в регистре во время компиляции, без магической строки), а не статический GetTypeметод. Так что пример кажется "нереальным".
Джеппе Стиг Нильсен,

4

Unity3D

public GameObject asset;

Тогда вы забудете перетащить ресурс туда и BOOM, Unity взрывается. Бывает все время.


3
подождите, для разработки в unity3d требуется мышь?
Эйнасио

1
@Einacio, если вы хотите, чтобы все было проще, вы можете просто перетащить актив в переменную. Очень удобно. Но вы можете писать без этого, если хотите.
Fabricio

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..xимеет х + 1 элементов и включает в себя х. Это означает, что мы превышаем границы массива. Другое дело, что когда вы делаете это в Ruby, это просто возвращает вас nilназад. Это очень весело, когда, скажем, ваша программа выбрасывает exceptдесять строк после ошибки.


Это слишком очевидно. Это как for (int i = 0; i <= arr.length; i++)в Java.
Коул Джонсон

3

Android

Я вижу, что это происходит слишком часто. Человек передает сообщение следующему действию (может быть, код состояния, данные карты и т. Д.) И в конечном итоге получает нулевое значение из 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

С

Просто быстрое чтение из буфера, где программист даже был достаточно любезен, чтобы задокументировать потенциальную опасность!

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

Очень простой и очевидный трюк, если вы ожидаете вредоносного кода. Конец комментария '\' экранирует символ новой строки, поэтому в буфере никогда не выделяется память. Затем он провалит сканирование, так как буфер будет иметь значение 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объявления локальной переменной в TNewable или в var(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!" быть напечатанным. Но вы увидите только мусор на экране.

Здесь a1 уничтожается, когда заканчивается init (). Таким образом, a_ptr, поскольку он указывает на a1, будет производить мусор.


0

С

#define ONE 0 + 1

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

объяснение

Препроцессор фактически не вычисляет 0 + 1, поэтому ONEбуквально определяется как 0 + 1, в результате чего 1 / 0 + 1, который является делением на ноль, и приводит к исключению с плавающей запятой.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.