Ném một ngoại lệ con trỏ null [đóng]


14

Nhiệm vụ của bạn là tạo ra một ngoại lệ con trỏ null. Nghĩa là, chương trình của bạn phải chấp nhận một giá trị mà nó dự kiến ​​là không null và đưa ra một ngoại lệ / lỗi hoặc sự cố vì giá trị này là null.

Hơn nữa, không thể rõ ràng khi đọc mã rằng giá trị là null. Mục tiêu của bạn là làm cho người đọc thấy rõ rằng giá trị không phải là null, mặc dù thực tế là như vậy.

  • Thay vì null, bạn có thể sử dụng nil, none, nothing hoặc bất cứ thứ gì tương đương trong ngôn ngữ của bạn. Bạn cũng có thể sử dụng không xác định, chưa được khởi tạo, v.v.
  • Vấn đề với mã của bạn phải là biến đó là (đáng ngạc nhiên) null trong đó chương trình mong đợi một biến không null.
  • Chương trình của bạn có thể phản hồi null bằng cách ném ngoại lệ, ném lỗi, sập hoặc bất cứ điều gì nó thường làm khi gặp null không mong muốn.

Đây là một cuộc thi phổ biến, vì vậy hãy thông minh!


@Ourous Bạn có thể cho một ví dụ để cho thấy những gì bạn có ý nghĩa?
Ypnypn

Sau khi xem qua, đó là một lỗi diễn viên nhiều hơn những gì bạn đang tìm kiếm.
urur

Tôi có được phép sử dụng một lỗi biên dịch không?
Đánh dấu

1
@Mark Đó là một cuộc thi phổ biến; hãy để cộng đồng quyết định Tôi chắc chắn sẽ bỏ phiếu cho một lỗi biên dịch.
11684

Câu trả lời:


33

Java

Hãy tính giá trị tuyệt đối của một số. Java có Math.abs cho mục đích này, tuy nhiên số lượng chúng tôi sử dụng đôi khi có thể là null. Vì vậy, chúng ta cần một phương thức trợ giúp để giải quyết trường hợp đó:

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

Nếu x là null, trả về null, khác sử dụng Math.abs ().
Mã này rất đơn giản và rõ ràng, và sẽ hoạt động tốt ... phải không?

Nhân tiện, sử dụng dòng này thay thế:

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

hoạt động chính xác. Tôi đã nghĩ về việc biến nó thành một spoiler, nhưng .. tôi nghĩ nó thật khó hiểu :)

Ok, một lời giải thích:

Đầu tiên, Math.abs không lấy một số nguyên, mà là một int (cũng có các phương thức bị quá tải cho các kiểu số khác) và cũng trả về một int. Trong java, int là kiểu nguyên thủy (và không thể là null) và Integer là lớp tương ứng của nó (và có thể là null). Kể từ phiên bản java 5, việc chuyển đổi giữa int và Integer được thực hiện tự động khi cần. Vì vậy, Math.abs có thể lấy x, tự động chuyển đổi thành int và trả về một int.

Bây giờ là phần kỳ lạ: khi toán tử ternary (? :) phải xử lý 2 biểu thức trong đó một biểu thức có kiểu nguyên thủy và biểu thức còn lại có lớp tương ứng (như int và Integer), người ta sẽ hy vọng rằng java sẽ chuyển đổi nguyên hàm đối với lớp (còn gọi là "quyền anh"), đặc biệt khi loại mà nó cần (ở đây để trả về từ phương thức) là loại tham chiếu (lớp) và biểu thức đầu tiên cũng thuộc loại tham chiếu. Nhưng java thực hiện ngược lại: nó chuyển đổi kiểu tham chiếu thành kiểu nguyên thủy (hay còn gọi là "unboxing"). Vì vậy, trong trường hợp của chúng tôi, nó sẽ cố gắng chuyển đổi x thành int, nhưng int không thể là null, do đó nó ném NPE.

Nếu bạn biên dịch mã này bằng Eclipse, bạn thực sự sẽ nhận được một cảnh báo: "Truy cập con trỏ không: Biểu thức kiểu Integer này là null nhưng yêu cầu tự động hủy hộp"


/programming/7811608/java-npe-in-ternary-operator-with-autoboxing
/programming/12763983/nullpulumexception-ENC-auto-boxing-behavior-of-java điều hành bên trong



SPOILERS Điều gì xảy ra khi x! = Null? (Tôi đã tìm ra rồi.)
11684

@ 11684 khi x không rỗng, nó hoạt động tốt
aditsu

Sau đó, tôi nghĩ rằng tôi không biết làm thế nào điều này hoạt động. Nếu nó hoạt động khi x không rỗng và tôi đúng về lý do tại sao điều này ném, dấu hai chấm phải được phân tích theo hai nghĩa (mà tôi sẽ không đưa vào Đặc tả ngôn ngữ).
11684

@ 11684 không chắc ý của bạn về "hai giác quan", nhưng dù sao tôi cũng đã thêm một lời giải thích ngay bây giờ
aditsu

23

C

Mỗi lập trình viên C đều mắc lỗi này, ít nhất một lần.

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

Lý do:

scanflấy một con trỏ ( int *) trong đối số, ở đây 0được truyền (con trỏ NULL)

Sửa chữa:

scanf("%d",&n);

http://ideone.com/MbQhMM


1
Nó không chính xác như thế. scanflà một hàm matrixdic và đối số của loại không chính xác ( int) đã được đưa ra, khi nó được mong đợi int *. Bởi vì C (tôi biết, trình biên dịch có thể phát hiện điều này trong trường hợp scanf) không thể kiểm tra các loại trong hàm matrixdic, điều này vui vẻ biên dịch. 0thực sự là con trỏ null theo nghĩa đen, nhưng điều này chỉ áp dụng cho chính chữ được sử dụng trực tiếp, mà không lưu trữ nó trong một biến. nkhông bao giờ chứa một con trỏ null
Konrad Borowski

Bạn cũng cần kiểm tra giá trị trả về của scanf để sửa lỗi này.
David Grayson

1
@David Không đúng nếu không kiểm tra giá trị trả về, nhưng nó sẽ không bị sập hoặc gọi UB - n sẽ vẫn ở mức 0. (Để xem điều này xảy ra, hãy thử chuyển hướng một tệp trống dưới dạng stdin.)
Đi xe đạp vào

14

PHP

Cái này đã cắn tôi vài lần.

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

Kết quả dự kiến:

Hello, World!

Kết quả thực tế:

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

aka NullPulumException

Lý do:

Theo mặc định, cú pháp hàm tạo tương thích ngược với PHP 4.x và do đó, hàm foolà hàm tạo hợp lệ cho lớp Foovà do đó ghi đè lên hàm tạo trống mặc định. Loại lỗi này có thể tránh được bằng cách thêm một không gian tên vào dự án của bạn.


9

CoffeeScript (trên Node.js)

Trong CoffeeScript, ?là toán tử hiện sinh. Nếu biến tồn tại, nó được sử dụng, nếu không thì phía bên tay phải đang được sử dụng. Chúng ta đều biết rằng thật khó để viết chương trình di động. Trong trường hợp, in bằng JavaScript được chỉ định. Trình duyệt sử dụng alert(hoặc document.write), SpiderMonkey sử dụng vỏ print, và sử dụng Node.js console.log. Điều này thật điên rồ, nhưng CoffeeScript giúp giải quyết vấn đề này.

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

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

Hãy chạy cái này dưới Node.js. Rốt cuộc, chúng tôi muốn đảm bảo kịch bản của chúng tôi hoạt động.

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, tại sao bạn lại phàn nàn về điều đó, khi nào alertcũng không được xác định?

Vì một số lý do, trong CoffeeScript, ?được kết hợp bên trái, có nghĩa là nó bỏ qua các biến không xác định chỉ dành cho phía bên trái. Nó không được trộn, bởi vì rõ ràng một số nhà phát triển có thể phụ thuộc vào? bị bỏ lại liên kết .


8

Hồng ngọc

Tìm awk trong PATH của một bản sao 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')

Giáo sư!

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

Từ lỗi, chúng tôi biết rằng p.findđược gọi nil.find, vì vậy pphải được nil. Làm sao chuyện này lại xảy ra?

Trong Ruby, defcó phạm vi riêng cho các biến cục bộ và không bao giờ lấy biến cục bộ từ phạm vi bên ngoài. Do đó, sự phân công p = ENV['PATH'].split ':'không nằm trong phạm vi.

Một biến không xác định thường gây ra NameError, nhưng plà một trường hợp đặc biệt. Ruby có một phương pháp toàn cầu được đặt tên p. Vì vậy, p.find { ... }trở thành một cuộc gọi phương thức, như p().find { ... }. Khi pkhông có đối số, nó trở lại nil. (Người chơi gôn mã sử dụng pnhư một lối tắt cho nil.) Sau đó nil.find { ... }tăng lên NoMethodError.

Tôi sửa nó bằng cách viết lại chương trình trong 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')))

Nó hoạt động!

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

Tôi có thể muốn nó in awk is /usr/bin/awk, nhưng đó là một lỗi khác.


7

C #

With()là một phương thức mở rộng cho stringđối tượng, về cơ bản chỉ là bí danh cho 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;
        }
    }
}

Có vẻ tốt, phải không? Sai lầm.

Type.GetType()yêu cầu một loại tên đầy đủ, phân biệt chữ hoa chữ thường. Vấn đề là System.stringkhông tồn tại; stringchỉ là một bí danh cho loại thực tế : System.String. Có vẻ như nó nên hoạt động, nhưng str.GetMethod()sẽ ném ngoại lệ vì str == null.

Hầu hết những người biết một chút về các chi tiết cụ thể bên trong của ngôn ngữ có thể sẽ có thể phát hiện ra vấn đề khá nhanh, nhưng đây vẫn là thứ dễ bị bỏ qua trong nháy mắt.


Đó là một điều tuyệt vời: D
Knerd

Điều này là một chút quá rõ ràng. Người đọc có được một đầu mối ngay lập tức vì có hai cách để làm điều tương tự .GetType()typeof()và do đó dẫn đến một lỗi khi kết quả là không giống nhau. Tôi nghĩ rằng người đăng muốn tìm mã bằng chứng.
ja72

Tại sao sự phản chiếu sẽ được sử dụng trong phương pháp mở rộng này? Các mã rõ ràng là return string.Format(format, args);. Nếu cần phản xạ (trong trường hợp sử dụng khác), người ta sẽ sử dụng typeof(string)(bắt lỗi trong thời gian biên dịch, không có chuỗi ma thuật), không phải GetTypephương thức tĩnh . Vì vậy, ví dụ có vẻ "không thực tế".
Jeppe Stig Nielsen

4

Unity3D

public GameObject asset;

Sau đó, bạn quên kéo và thả tài sản ở đó và BÙM, Unity phát nổ. Xảy ra mọi lúc.


3
chờ đã, phát triển trong unity3d có cần chuột không?
Einacio

1
@Einacio nếu bạn muốn mọi thứ dễ dàng hơn, bạn chỉ cần kéo một tài sản vào một biến. Rất tiện dụng. Nhưng bạn có thể mã mà không cần điều đó nếu bạn muốn.
Fabricio

4

Hồng ngọc

Chỉ cần một số mã đơn giản để lấy sản phẩm của một mảng.

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

Hai điều ở đây. Một là các ..nhà điều hành phạm vi được bao gồm . Vì vậy, 0..xx + 1 phần tử và bao gồm x. Điều này có nghĩa là chúng tôi vượt quá giới hạn của mảng. Một điều khác là khi bạn làm điều này trong Ruby, nó sẽ giúp bạn niltrở lại. Điều này rất thú vị khi, giả sử, chương trình của bạn ném exceptmười dòng sau lỗi.


Điều này là quá rõ ràng. Nó giống như làm for (int i = 0; i <= arr.length; i++)trong Java.
Cole Johnson

3

Android

Tôi thấy điều này xảy ra quá thường xuyên. Một người chuyển một tin nhắn đến hoạt động tiếp theo (có thể là mã trạng thái, dữ liệu bản đồ, bất cứ thứ gì) và cuối cùng rút một null từIntent .

Thoạt nhìn có vẻ khá hợp lý. Chỉ:

  • đảm bảo tin nhắn không rỗng
  • đóng gói nó vào ý định
  • bắt đầu hoạt động mới
  • có ý định trong hoạt động mới
  • trích xuất tin nhắn bằng thẻ

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

Trong 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 không nói rằng Intent.getStringExtra(String)có thể trở lại null, nhưng chỉ nếu thẻ không được tìm thấy. Rõ ràng tôi đang sử dụng cùng một thẻ, vì vậy nó phải là một thứ khác ...


2
Vấn đề với câu trả lời này là những người không quen thuộc với sự phát triển của Android (như tôi) sẽ không thể đánh giá cao nó.
John Dvorak

@JanDvorak Đồng ý, nhưng không có vấn đề gì lớn. Có những câu trả lời khác trên trang web mà tôi cũng không hoàn toàn đánh giá cao. :)
Geobits

3

C

Chỉ cần đọc nhanh từ bộ đệm, nơi lập trình viên thậm chí đã đủ tử tế để ghi lại mối nguy hiểm tiềm tàng!

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

Thủ thuật rất đơn giản và rõ ràng nếu bạn đang mong đợi mã độc. Nhận xét kết thúc '\' thoát khỏi dòng mới, vì vậy bộ đệm không bao giờ được cấp phát bộ nhớ. Sau đó, nó sẽ không quét được, vì bộ đệm sẽ là NULL (vì các biến phạm vi tệp không được khởi tạo trong 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)

Điều gì đang xảy ra ở đây là một chút phức tạp. Trong Nimrod, các thủ tục được khởi tạo mặc định thành nil. Trong make_funccuộc gọi đầu tiên , nó đã thành công. Thứ hai, tuy nhiên, ném một ngoại lệ và để lại f2chưa được khởi tạo. Sau đó, nó được gọi, gây ra lỗi.


0

C #

Đây là cổ điển. Các rất phương pháp hữu ích FindStringRepresentationOfsẽ tạo ra một thể hiện mới của các tham số kiểu nhất định, sau đó tìm ra chuỗi đại diện của trường hợp đó.

Chắc chắn sẽ rất lãng phí nếu kiểm tra nullngay lập tức sau khi tạo một thể hiện mới, vì vậy tôi đã không làm điều đó ...

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

Thay đổi objecttrong khai báo biến cục bộ thành TNewable hoặc thành var(C # 3 trở lên) làm cho vấn đề biến mất. Gợi ý: Quyền anh của Nullable<T>(aka T?) là bất thường trong .NET Framework.

Sau khi đã khắc phục sự cố như được mô tả trong văn bản vô hình ở trên, hãy thử goodInstance.GetType()(sự khác biệt là GetType(), không giống như ToString()là không ảo, vì vậy họ không thể giải quyết overridevấn đề này 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;
}

Những gì bạn mong đợi là "Xin chào thế giới!" Được in. Nhưng bạn sẽ chỉ thấy rác trên màn hình.

Ở đây a1 bị hủy khi phạm vi init () kết thúc. Vì vậy, a_ptr, khi nó trỏ đến a1, sẽ tạo ra rác.


0

C

#define ONE 0 + 1

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

Giải trình

Bộ tiền xử lý không thực sự tính toán 0 + 1, do đó ONEđược định nghĩa theo nghĩa đen là 0 + 1, dẫn đến 1 / 0 + 1, đó là một phép chia cho 0 và dẫn đến một ngoại lệ dấu phẩy động.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.