Викиньте виключення з нульовим покажчиком [закрито]


14

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

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

  • Замість null ви можете використовувати nil, none, нічого або будь-який еквівалент у вашій мові. Ви також можете використовувати невизначені, неініціалізовані тощо.
  • Проблема з вашим кодом повинна полягати в тому, що змінна (на диво) нульова, коли програма очікує ненульову змінну.
  • Ваша програма може відповісти на нуль, кинувши виняток, викинувши помилку, збившись або все, що вона зазвичай робить, коли виникає несподіваний нуль.

Це конкурс популярності, тому будьте розумні!


@Ourous Чи можете ви навести приклад, щоб показати, що ви маєте на увазі?
Ypnypn

Подивившись на це, це більше помилка лиття, ніж те, що ви шукаєте.
Οurous

Чи можна використовувати помилку компілятора?
Марк

1
@Mark Це конкурс популярності; нехай громада вирішить. Я б точно проголосував за помилку компілятора.
11684 р

Відповіді:


33

Java

Обчислимо абсолютне значення числа. У 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 є null, поверніть null, інакше використовуйте 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.

Тепер дивна частина: коли потрійний оператор (? :) має справу з двома виразами, коли один має примітивний тип, а інший має відповідний клас (наприклад, int та Integer), можна було б очікувати, що java перетворить примітивний до класу (він же "бокс"), особливо коли тип, який йому потрібен (тут для повернення з методу), є еталонним (класовим) типом, і перший вираз також є референтним типом. Але java робить зовсім навпаки: він перетворює тип посилання в примітивний тип (він же "unboxing"). Отже, у нашому випадку воно намагатиметься перетворити x в int, але int не може бути null, таким чином воно кидає NPE.

Якщо ви компілюєте цей код за допомогою Eclipse, ви дійсно отримаєте попередження: "Нульовий доступ до вказівника: Цей вираз типу Integer є нульовим, але вимагає автоматичного розпакування"


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



ШПОЙЛЕРИ Що відбувається, коли x! = Null? (Я вже зрозумів це.)
11684

@ 11684, коли x не є нульовим, він працює чудово
aditsu кине, тому що SE - EVIL

Тоді я думаю, що не знаю, як це працює. Якщо це працює, коли x не є нульовим, і я маю рацію щодо того, чому це кидає, товсту кишку потрібно розібрати в двох сенсах (що я б не вказав у мовній специфікації).
11684

@ 11684 не впевнений, що ти маєш на увазі про "два почуття", але все одно я додав пояснення зараз
aditsu кинь, тому що 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бере аргумент pointer ( int *), тут 0передається (NULL-pointer)

Виправити:

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

aka 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 в PATH клону 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
Кнерд

Це трохи занадто очевидно. Читач отримати негайний ключ , тому що є два способи зробити те ж саме .GetType()і typeof()та , таким чином , що призводить до несправності , якщо результат не те ж саме. Я думаю, що плакат хотів отримати куленепробивний код, якого немає.
ja72

Чому б рефлексія використовувалася в цьому методі розширення? Очевидний код є return string.Format(format, args);. Якщо потрібна рефлексія (в іншому випадку використання), використовувались би typeof(string)(ловить помилки корпусу під час компіляції, не магічна рядок), а не статичний GetTypeметод. Тож приклад здається "нереальним".
Jeppe Stig Nielsen

4

Unity3D

public GameObject asset;

Тоді ви забудете перетягнути актив туди і БУМ, Unity вибухає. Відбувається весь час.


3
зачекайте, для розвитку в unit3d потрібна миша?
Ейнасіо

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є елементи x + 1 і включає x. Це означає, що ми перевищуємо межі масиву. Інша справа, що коли ви робите це в 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 Погодився, але нічого великого. На сайті є інші відповіді, які я також не ціную в повній мірі. :)
Геобіць

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

Дуже простий і очевидний трюк, якщо ви очікуєте шкідливого коду. Коментар, що закінчується "\", уникає нового рядка, тому буфер ніколи не виділяє пам'ять. Тоді він не зможе 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декларації локальної змінної в TNewable або в var(C # 3 і пізніші) змушує проблему усунутись. Підказка: Бокс Nullable<T>(ака 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.