什么是空引用异常(NullReferenceException),如何修复?

1869

我有一些代码,当它执行时,抛出了一个 NullReferenceException 异常,显示:

对象引用未设置为对象的实例。

这是什么意思?我该如何修复此错误?


在VS 2017中的异常助手将更有帮助,可以诊断此异常的原因--https://blogs.msdn.microsoft.com/visualstudio/2016/11/28/productivity-in-visual-studio-2017-rc/下的“新异常助手”。 - Zev Spitz
亲爱的未来访客,这个问题的答案同样适用于ArgumentNullException。如果你的问题已被关闭并列为重复的问题,并且你正在遇到ANE,请按照答案中的指示进行调试和修复问题。 - user1228
@will,ANE 只有在传递 null 作为参数时才会发生。你能给出一个 ANE 问题被关闭,并作为此问题的副本的示例吗? - John Saunders
这个问题在Meta上出现过,但我得去找链接。至于那个评论,ANE只是NRE的一种,但有人添加了一个预防性检查,而且你至少知道哪里为空(参数名已提供),所以诊断起来比直接的NRE要容易一些。 - user1228
27个回答

2709

什么是原因?

底线

您正在尝试使用一个null(或在VB.NET中为Nothing)的东西。这意味着您要么将其设置为null,要么根本没有设置它。

像其他任何东西一样,null会被传递。如果它在方法“A”中是null,那么可能是方法“B”向方法“A”传递了一个null

null可以有不同的含义:

  1. 对象变量未初始化,因此指向。在这种情况下,如果您访问这些对象的成员,则会导致NullReferenceException
  2. 开发人员有意使用null来表示没有可用的有意义的值。请注意,C#具有可为空的数据类型概念,用于变量(例如数据库表可以有可为空的字段)- 您可以将null分配给它们以指示其中没有存储任何值,例如int?a = null;(这是Nullable<int> a = null;的快捷方式),其中问号表示允许在变量a中存储null。您可以使用if(a.HasValue){...}if(a==null){...}进行检查。像此示例中的a一样的可空变量允许通过a.Value显式访问该值,或者像普通变量一样通过a访问该值。
    请注意,如果anull,则通过a.Value访问它会引发InvalidOperationException而不是NullReferenceException - 您应该先进行检查,即如果您有另一个非空变量int b;,则应进行类似if(a.HasValue){b=a.Value;}或更短的if(a!=null){b=a;}的赋值。
本文的其余部分将更详细地介绍许多程序员经常犯的错误,这些错误可能会导致NullReferenceException
更具体地说,runtime抛出NullReferenceException始终意味着同一件事:您正在尝试使用引用,但该引用未初始化(或曾经初始化,但现在未初始化)。
这意味着该引用为null,您无法通过null引用访问成员(例如方法)。最简单的情况:
string foo = null;
foo.ToUpper();

第二行将抛出 NullReferenceException ,因为您不能在指向 null string 引用上调用实例方法 ToUpper()

调试

如何找到 NullReferenceException 源代码?除了查看异常本身外,Visual Studio中的调试一般规则适用:放置关键断点并检查您的变量,可以通过将鼠标悬停在名称上、打开(快速)监视窗口或使用各种调试面板(例如Locals和Autos)来进行。

如果要查找引用的设置位置,请右键单击其名称并选择“查找所有引用”。然后,您可以在每个找到的位置处放置断点并带有附加调试器运行程序。每次调试器在此类断点上暂停时,您需要确定是否期望引用为非null,检查变量并验证它在您期望的时间指向一个对象实例。

通过以这种方式跟踪程序流程,可以找到实例不应为null的位置以及为什么未正确设置实例。

示例

可能会抛出异常的一些常见情况:

通用

ref1.ref2.ref3.member

如果ref1或ref2或ref3为空,则会出现NullReferenceException异常。如果你想解决这个问题,那么通过将表达式重写为更简单的等价形式来找出哪一个是空的即可:
var r1 = ref1;
var r2 = r1.ref2;
var r3 = r2.ref3;
r3.member

具体来说,在HttpContext.Current.User.Identity.Name中,HttpContext.Current可能为null,或者User属性可能为null,或者Identity属性可能为null。

间接的

public class Person 
{
    public int Age { get; set; }
}
public class Book 
{
    public Person Author { get; set; }
}
public class Example 
{
    public void Foo() 
    {
        Book b1 = new Book();
        int authorAge = b1.Author.Age; // You never initialized the Author property.
                                       // there is no Person to get an Age from.
    }
}

如果您想避免子对象(Person)的空引用,您可以在父对象(Book)的构造函数中初始化它。
嵌套对象初始化器也适用于相同的规则:
Book b1 = new Book 
{ 
   Author = { Age = 45 } 
};

这翻译成:

Book b1 = new Book();
b1.Author.Age = 45;
< p > 当使用new关键字时,它只会创建一个Book的新实例,但不会创建Person的新实例,因此Author属性仍然为null

嵌套集合初始化程序

public class Person 
{
    public ICollection<Book> Books { get; set; }
}
public class Book 
{
    public string Title { get; set; }
}

嵌套集合Initializers的行为相同:

Person p1 = new Person 
{
    Books = {
         new Book { Title = "Title1" },
         new Book { Title = "Title2" },
    }
};

这个翻译成:

Person p1 = new Person();
p1.Books.Add(new Book { Title = "Title1" });
p1.Books.Add(new Book { Title = "Title2" });

new Person 只创建了一个 Person 的实例,但是 Books 集合仍然为 null。集合的初始化语法并不会为 p1.Books 创建一个集合,它只会转换成 p1.Books.Add(...) 语句。

数组

int[] numbers = null;
int n = numbers[0]; // numbers is null. There is no array to index.

数组元素

Person[] people = new Person[5];
people[0].Age = 20 // people[0] is null. The array was allocated but not
                   // initialized. There is no Person to set the Age for.

锯齿数组

long[][] array = new long[1][];
array[0][0] = 3; // is null because only the first dimension is yet initialized.
                 // Use array[0] = new long[2]; first.

集合/列表/字典

Dictionary<string, int> agesForNames = null;
int age = agesForNames["Bob"]; // agesForNames is null.
                               // There is no Dictionary to perform the lookup.

范围变量(间接/延迟)

public class Person 
{
    public string Name { get; set; }
}
var people = new List<Person>();
people.Add(null);
var names = from p in people select p.Name;
string firstName = names.First(); // Exception is thrown here, but actually occurs
                                  // on the line above.  "p" is null because the
                                  // first element we added to the list is null.

事件(C#)

public class Demo
{
    public event EventHandler StateChanged;
    
    protected virtual void OnStateChanged(EventArgs e)
    {        
        StateChanged(this, e); // Exception is thrown here 
                               // if no event handlers have been attached
                               // to StateChanged event
    }
}

注意:VB.NET编译器会为事件使用插入空值检查,因此在VB.NET中不需要检查事件是否为Nothing

错误的命名约定:

如果您将字段命名与本地变量不同,您可能会意识到从未初始化该字段。

public class Form1
{
    private Customer customer;
    
    private void Form1_Load(object sender, EventArgs e) 
    {
        Customer customer = new Customer();
        customer.Name = "John";
    }
    
    private void Button_Click(object sender, EventArgs e)
    {
        MessageBox.Show(customer.Name);
    }
}

可以通过遵循前缀字段下划线的约定来解决此问题:

    private Customer _customer;

ASP.NET页面生命周期:
public partial class Issues_Edit : System.Web.UI.Page
{
    protected TestIssue myIssue;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
             // Only called on first load, not when button clicked
             myIssue = new TestIssue(); 
        }
    }
        
    protected void SaveButton_Click(object sender, EventArgs e)
    {
        myIssue.Entry = "NullReferenceException here!";
    }
}

ASP.NET会话值

// if the "FirstName" session value has not yet been set,
// then this line will throw a NullReferenceException
string firstName = Session["FirstName"].ToString();

ASP.NET MVC空视图模型

如果在ASP.NET MVC View中引用@Model的属性时发生异常,您需要了解Model是在操作方法中设置的,当您return一个视图时。 当您从控制器返回一个空模型(或模型属性)时,当视图访问它时会发生异常:

// Controller
public class Restaurant:Controller
{
    public ActionResult Search()
    {
        return View();  // Forgot the provide a Model here.
    }
}

// Razor view 
@foreach (var restaurantSearch in Model.RestaurantSearch)  // Throws.
{
}
    
<p>@Model.somePropertyName</p> <!-- Also throws -->

WPF控件创建顺序和事件

WPF控件在调用InitializeComponent时按照视觉树中的顺序创建。如果早期创建的控件具有事件处理程序等,在InitializeComponent期间引用后期创建的控件,将会引发NullReferenceException

例如:

<Grid>
    <!-- Combobox declared first -->
    <ComboBox Name="comboBox1" 
              Margin="10"
              SelectedIndex="0" 
              SelectionChanged="comboBox1_SelectionChanged">
       <ComboBoxItem Content="Item 1" />
       <ComboBoxItem Content="Item 2" />
       <ComboBoxItem Content="Item 3" />
    </ComboBox>
        
    <!-- Label declared later -->
    <Label Name="label1" 
           Content="Label"
           Margin="10" />
</Grid>

这里在创建label1之前就已经创建了comboBox1。如果comboBox1_SelectionChanged试图引用`label1`,它将尚未被创建。

private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    label1.Content = comboBox1.SelectedIndex.ToString(); // NullReferenceException here!!
}

更改在XAML中声明的顺序(即,将label1列在comboBox1之前,忽略设计哲学问题)至少可以解决此处的NullReferenceException

使用as进行强制类型转换

var myThing = someObject as Thing;

这不会抛出一个 InvalidCastException,但在转换失败时(以及someObject本身为null时)返回一个null。因此请注意。

LINQ FirstOrDefault()SingleOrDefault()

普通版本的First()Single()在没有内容时会抛出异常。 "OrDefault"版本在这种情况下返回null。因此请注意。

foreach

当您尝试迭代null集合时,foreach会抛出异常。通常是由返回集合的方法意外返回null引起的。
List<int> list = null;    
foreach(var v in list) { } // NullReferenceException here

更为实际的例子 - 从XML文档中选择节点。 如果未找到节点,将抛出异常,但初始调试显示所有属性都有效:

foreach (var node in myData.MyXml.DocumentNode.SelectNodes("//Data"))

避免的方法

明确检查null并忽略null值。

如果您预计引用有时会是null,则可以在访问实例成员之前检查其是否为null

void PrintName(Person p)
{
    if (p != null) 
    {
        Console.WriteLine(p.Name);
    }
}

明确检查null并提供默认值。

你调用的方法可能会返回null,例如当寻找的对象不存在时。在这种情况下,你可以选择返回一个默认值:

string GetCategory(Book b) 
{
    if (b == null)
        return "Unknown";
    return b.Category;
}

明确检查方法调用的null并抛出自定义异常。

您还可以抛出自定义异常,仅在调用代码中捕获:

string GetCategory(string bookTitle) 
{
    var book = library.FindBook(bookTitle);  // This may return null
    if (book == null)
        throw new BookNotFoundException(bookTitle);  // Your custom exception
    return book.Category;
}

如果一个值永远不应该是null,请使用Debug.Assert来捕获问题,以便在异常发生之前更早地捕获问题。

当您在开发过程中知道一个方法可能会返回null,但永远不应该返回时,可以使用Debug.Assert(),以便在它出现时尽早中断:

string GetTitle(int knownBookID) 
{
    // You know this should never return null.
    var book = library.GetBook(knownBookID);  

    // Exception will occur on the next line instead of at the end of this method.
    Debug.Assert(book != null, "Library didn't return a book for known book ID.");

    // Some other code

    return book.Title; // Will never throw NullReferenceException in Debug mode.
}

尽管此检查不会出现在您的发布版本中, 但当book == null在发布模式下运行时,它将再次引发NullReferenceException

对于nullable值类型,请使用GetValueOrDefault()提供默认值,以防它们为null

DateTime? appointment = null;
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the default value provided (DateTime.Now), because appointment is null.

appointment = new DateTime(2022, 10, 20);
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the appointment date, not the default

使用空值合并运算符:??(C#)或If()(VB)。

当遇到null时,提供默认值的简写方式:

IService CreateService(ILogger log, Int32? frobPowerLevel)
{
   var serviceImpl = new MyService(log ?? NullLog.Instance);
 
   // Note that the above "GetValueOrDefault()" can also be rewritten to use
   // the coalesce operator:
   serviceImpl.FrobPowerLevel = frobPowerLevel ?? 5;
}

使用空条件运算符:?.?[x]处理数组(在C# 6和VB.NET 14中可用):

这也有时被称为安全导航或Elvis(因其形状而得名)运算符。如果运算符左侧的表达式为空,则不会评估右侧,并返回null。这意味着像这样的情况:

var title = person.Title.ToUpper();

如果该人没有头衔,这将抛出异常,因为它试图在具有空值的属性上调用ToUpper
C# 5及以下版本中,可以使用以下方法进行保护:
var title = person.Title == null ? null : person.Title.ToUpper();

现在,标题变量将为空,而不是抛出异常。C#6引入了一种更短的语法:

var title = person.Title?.ToUpper();

这将导致标题变量为null,如果person.Titlenull,则不会调用ToUpper
当然,您仍然需要检查title是否为null或使用空条件运算符与空合并运算符(??)一起提供默认值:
// regular null check
int titleLength = 0;
if (title != null)
    titleLength = title.Length; // If title is null, this would throw NullReferenceException
    
// combining the `?` and the `??` operator
int titleLength = title?.Length ?? 0;

同样地,对于数组,您可以使用以下方式?[i]
int[] myIntArray = null;
var i = 5;
int? elem = myIntArray?[i];
if (!elem.HasValue) Console.WriteLine("No value");

这将做以下操作:如果myIntArraynull,则表达式返回null,您可以安全地进行检查。如果它包含一个数组,则执行以下操作:elem = myIntArray[i];并返回第i个元素。

使用空上下文(在C#8中可用):

C#8中引入的空上下文和可为空引用类型对变量执行静态分析,并在值可能为null或已设置为null时提供编译器警告。可为空的引用类型允许明确允许类型为null

可以使用csproj文件中的Nullable元素为项目设置可空性注释上下文和可空性警告上下文。此元素配置编译器如何解释类型的可空性以及生成哪些警告。有效设置为:

  • enable: 可空注释上下文已启用。可空警告上下文已启用。引用类型变量(例如字符串)是非空的。所有空值警告都已启用。
  • disable: 可空注释上下文已禁用。可空警告上下文已禁用。引用类型变量是无意识的,就像早期版本的C#一样。所有空值警告都已禁用。
  • safeonly: 可空注释上下文已启用。可空警告上下文已设置为仅安全。引用类型变量是非空的。所有安全空值警告都已启用。
  • warnings: 可空注释上下文已禁用。可空警告上下文已启用。引用类型变量是无意识的。所有空值警告都已启用。
  • safeonlywarnings: 可空注释上下文已禁用。可空警告上下文已设置为仅安全。引用类型变量是无意识的。所有安全空值警告都已启用。

可空引用类型使用与可空值类型相同的语法来标记:在变量类型后添加?

调试和修复迭代器中的空解引用的特殊技术

C#支持“迭代器块”(在一些其他流行的语言中称为“生成器”)。由于延迟执行,NullReferenceException在迭代器块中可能特别难以调试:

public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
    for (int i = 0; i < count; ++i)
    yield return f.MakeFrob();
}
...
FrobFactory factory = whatever;
IEnumerable<Frobs> frobs = GetFrobs();
...
foreach(Frob frob in frobs) { ... }

如果whatever的结果为null,那么MakeFrob会抛出异常。现在,你可能认为正确的做法是这样的:
// DON'T DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
   if (f == null) 
      throw new ArgumentNullException("f", "factory must not be null");
   for (int i = 0; i < count; ++i)
      yield return f.MakeFrob();
}

这个为什么是错误的呢?因为迭代器块直到 foreach 才会被执行!调用 GetFrobs 只是返回一个对象,该对象在迭代时将运行迭代器块。

像这样编写 null 检查可以防止 NullReferenceException,但是你将 NullArgumentException 移动到迭代点而不是调用点,这使得调试非常困难

正确的修复方法如下:

// DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
   // No yields in a public method that throws!
   if (f == null) 
       throw new ArgumentNullException("f", "factory must not be null");
   return GetFrobsForReal(f, count);
}
private IEnumerable<Frob> GetFrobsForReal(FrobFactory f, int count)
{
   // Yields in a private method
   Debug.Assert(f != null);
   for (int i = 0; i < count; ++i)
        yield return f.MakeFrob();
}

即创建一个私有的辅助方法,其中包含迭代器块逻辑和一个公共的表面方法,用于进行null检查并返回迭代器。现在当调用GetFrobs时,null检查会立即发生,然后当迭代序列时,GetFrobsForReal将被执行。
如果您查看LINQ到对象的参考源代码,就会看到这种技术被广泛使用。它写起来略微笨重,但可以更轻松地调试空值错误。优化您的代码以方便调用者,而不是作者的方便。
关于不安全代码中的空解引用的说明 C#有一种"不安全"模式,正如其名称所示,非常危险,因为不执行提供内存安全性和类型安全性的正常安全机制。除非您对内存工作原理有深入彻底的了解,否则不应编写不安全代码
在不安全模式下,您应该注意两个重要事实:
- 解引用空指针会产生与解引用空引用相同的异常 - 在某些情况下,解引用无效的非空指针可能会产生该异常
要理解为什么会这样,有助于了解.NET如何首先产生NullReferenceException。(这些细节适用于在Windows上运行的.NET;其他操作系统使用类似的机制。)
在Windows中,内存是虚拟化的;每个进程都有一个由操作系统跟踪的许多"页面"的虚拟内存空间。每个内存页面都有设置的标志,用于确定它如何被使用:读取、写入、执行等等。最低的内存页面被标记为"如果以任何方式使用则产生错误"。
在C#中,空指针和空引用都被内部表示为数字零,因此任何试图将其解引用为其相应的内存存储的尝试都会导致操作系统产生错误。然后.NET运行时检测到这个错误并将其转换为NullReferenceException。
这就是为什么解引用空指针和空引用都会产生相同异常的原因。
那第二点呢?对任何无效指针进行解引用,只要它落在虚拟内存的最低页面中,就会导致相同的操作系统错误,从而产生相同的异常。
为什么这样做有意义呢?假设我们有一个包含两个int和一个等于null的非托管指针的结构体。如果我们尝试解引用结构体中的第二个int,则CLR不会尝试访问位置零处的存储;它将访问位置四处的存储。但从逻辑上讲,这是一个空解引用,因为我们是通过null到达该地址。
如果你正在使用不安全的代码并且遇到NullReferenceException,请注意有问题的指针可能不是null。它可以是虚拟内存的最低页面中的任何位置,并且会产生此异常。

64
也许这是一个愚蠢的评论,但是避免这个问题的第一种和最好的方法不是初始化对象吗?对我来说,如果出现这种错误,通常是因为我忘记初始化某些东西,比如数组元素。我认为定义对象为空然后引用它要少得多。也许在描述旁边给出解决每个问题的方法会更好。仍然是一篇好文章。 - JPK
34
如果没有对象,而是从一个方法或属性返回值,会怎样呢? - John Saunders
8
这个书籍/作者的例子有点奇怪... 它是如何编译的?智能感知是如何工作的?这是什么,我不太懂电脑... - user1228
5
@Will:我的最后一次编辑有帮助吗?如果没有,请更明确地说明您看到的问题是什么。 - John Saunders
6
@JohnSaunders 噢,不好意思,我指的是那个对象初始化器版本。new Book { Author = { Age = 45 } }; 内部初始化是如何工作的...我想不出任何内部初始化会起作用的情况,但它可以编译并且 IntelliSense 也能正常工作...除非是对于结构体? - user1228
显示剩余4条评论

330

NullReference Exception — Visual Basic

NullReference Exception(空引用异常)在 Visual Basic 中和在 C# 中没有区别。毕竟,它们都报告了由它们共同使用的.NET Framework定义的相同异常。唯一与Visual Basic相关的原因很少见(也许只有一个)。

本答案将使用Visual Basic术语、语法和上下文。所使用的示例来自于大量过去的Stack Overflow问题。这是为了通过使用通常在帖子中出现的类型的情况最大限度地提高相关性。对于那些可能需要的人,还提供了更多的解释。类似于您的示例非常可能在此处列出。

注意:

  1. 这是基于概念的:没有代码可以粘贴到您的项目中。它旨在帮助您理解什么导致了NullReferenceException(NRE),如何找到它,如何修复它以及如何避免它。 NRE可能会以多种方式引起,因此这不太可能是您唯一的遭遇。
  2. 示例(来自Stack Overflow帖子)并不总是展示首选的操作方法。
  3. 通常使用最简单的补救措施。

基本含义

消息"Object not set to an instance of Object"(对象未设置为对象的实例)意味着您正在尝试使用未初始化的对象。这可以归结为以下几点:

  • 您的代码声明了一个对象变量,但没有初始化它(创建一个实例或'实例化'它)
  • 您的代码假设某些东西会初始化对象,但实际上没有
  • 可能是其他代码过早地使仍在使用中的对象无效

查找原因

由于问题是一个引用为Nothing的对象,因此答案是检查它们以找出哪一个。然后确定为什么它没有被初始化。将鼠标悬停在各个变量上,Visual Studio(VS)将显示它们的值——罪魁祸首将是Nothing

IDE debug display

您还应该从相关代码中删除任何Try/Catch块,特别是其中Catch块中没有任何内容的情况。这将导致您的代码在尝试使用一个为Nothing的对象时崩溃。 这正是您想要的,因为它将确定问题的确切位置,并允许您确定导致它的对象。

在Catch中显示Error while...MsgBox将没有太大帮助。此方法还会导致非常糟糕的Stack Overflow问题,因为您无法描述实际异常、涉及的对象甚至发生它的代码行。

您还可以使用“Locals Window”(调试 -> 窗口 -> 局部变量)来检查您的对象。
一旦您知道问题所在,通常很容易修复,并且比发布新问题更快。
另请参阅:

示例和解决方法

类对象/创建一个实例

Dim reg As CashRegister
...
TextBox1.Text = reg.Amount         ' NRE

问题在于Dim并没有创建一个CashRegister 对象,它只是声明了一个名为reg的该类型变量。声明一个对象变量和创建一个实例是两个不同的操作。

解决方法

通常可以在声明时使用New运算符来创建实例:

Dim reg As New CashRegister        ' [New] creates instance, invokes the constructor

' Longer, more explicit form:
Dim reg As CashRegister = New CashRegister

当只有在稍后才创建实例时:

Private reg As CashRegister         ' Declare
  ...
reg = New CashRegister()            ' Create instance

注意: 不要在过程中再次使用Dim,包括构造函数(Sub New):
Private reg As CashRegister
'...

Public Sub New()
   '...
   Dim reg As New CashRegister
End Sub

这将创建一个局部变量reg,仅存在于该上下文(子函数)中。模块级别Scopereg变量在其他任何地方都将保持为Nothing

在审查的Stack Overflow问题中,缺少New运算符是空引用异常的首要原因

Visual Basic尝试使用New反复澄清这个过程:使用New运算符创建一个对象,调用Sub New——构造函数——在这里,您的对象可以执行任何其他初始化。

需要明确的是,Dim(或Private)只是声明一个变量及其类型。变量的作用域——无论它是否存在于整个模块/类中,或者仅存在于过程中——由声明位置确定。Private | Friend | Public定义访问级别,而不是范围
更多信息,请参阅:

数组

数组也必须被实例化:
Private arr as String()

这个数组只被声明了,但没有被创建。有几种方法可以初始化一个数组:

Private arr as String() = New String(10){}
' or
Private arr() As String = New String(10){}

' For a local array (in a procedure) and using 'Option Infer':
Dim arr = New String(10) {}

注意:从VS 2010开始,当使用文字字面量和Option Infer初始化本地数组时,As <Type>New元素是可选的:

Dim myDbl As Double() = {1.5, 2, 9.9, 18, 3.14}
Dim myDbl = New Double() {1.5, 2, 9.9, 18, 3.14}
Dim myDbl() = {1.5, 2, 9.9, 18, 3.14}

数据类型和数组大小取决于所分配的数据。类/模块级别的声明仍需要 As <Type> 以及 Option Strict

Private myDoubles As Double() = {1.5, 2, 9.9, 18, 3.14}

示例:类对象数组

Dim arrFoo(5) As Foo

For i As Integer = 0 To arrFoo.Count - 1
   arrFoo(i).Bar = i * 10       ' Exception
Next

数组已经创建,但其中的Foo对象还未被创建。
解决方法:
For i As Integer = 0 To arrFoo.Count - 1
    arrFoo(i) = New Foo()         ' Create Foo instance
    arrFoo(i).Bar = i * 10
Next

使用 List(Of T) 会使得没有有效对象的元素变得相当困难:

Dim FooList As New List(Of Foo)     ' List created, but it is empty
Dim f As Foo                        ' Temporary variable for the loop

For i As Integer = 0 To 5
    f = New Foo()                    ' Foo instance created
    f.Bar =  i * 10
    FooList.Add(f)                   ' Foo object added to list
Next

更多信息,请参见:


列表和集合

.NET集合(有许多种类 - 列表,字典等)也必须被实例化或创建。

Private myList As List(Of String)
..
myList.Add("ziggy")           ' NullReference

您因同样的原因得到了相同的异常 - myList仅被声明,但没有创建实例。解决方法也是相同的:
myList = New List(Of String)

' Or create an instance when declared:
Private myList As New List(Of String)

常见的疏忽是使用集合Type的类:
Public Class Foo
    Private barList As List(Of Bar)

    Friend Function BarCount As Integer
        Return barList.Count
    End Function

    Friend Sub AddItem(newBar As Bar)
        If barList.Contains(newBar) = False Then
            barList.Add(newBar)
        End If
    End Function

无论使用哪种方法,都会导致NRE,因为barList只是声明而不是实例化。创建Foo的实例并不会同时创建内部的barList实例。可能构造函数中本意就是要这样做:

Public Sub New         ' Constructor
    ' Stuff to do when a new Foo is created...
    barList = New List(Of Bar)
End Sub

像之前一样,这是不正确的:

Public Sub New()
    ' Creates another barList local to this procedure
     Dim barList As New List(Of Bar)
End Sub

欲了解更多信息,请参见List(Of T)


数据提供程序对象

与数据库一起工作会出现许多空引用的机会,因为可以同时使用许多对象(CommandConnectionTransactionDatasetDataTableDataRows等)。注意:无论您使用哪个数据提供程序--MySQL、SQL Server、OleDB等--概念都是相同的。

示例1

Dim da As OleDbDataAdapter
Dim ds As DataSet
Dim MaxRows As Integer

con.Open()
Dim sql = "SELECT * FROM tblfoobar_List"
da = New OleDbDataAdapter(sql, con)
da.Fill(ds, "foobar")
con.Close()

MaxRows = ds.Tables("foobar").Rows.Count      ' Error

与之前一样,声明了ds数据集对象,但没有创建实例。 DataAdapter将填充现有的DataSet,而不是创建一个新的。 在这种情况下,由于ds是局部变量,IDE会警告您可能会发生这种情况:

img

当作为模块/类级别变量声明时,如con所示,编译器无法知道对象是否是由上游过程创建的。 不要忽略警告。 解决方法
Dim ds As New DataSet

例子 2

ds = New DataSet
da = New OleDBDataAdapter(sql, con)
da.Fill(ds, "Employees")

txtID.Text = ds.Tables("Employee").Rows(0).Item(1)
txtID.Name = ds.Tables("Employee").Rows(0).Item(2)

一个错别字问题在这里: Employees vs Employee。没有名为“Employee”的DataTable被创建,因此尝试访问它会导致NullReferenceException。另一个潜在的问题是假设将存在Items,当SQL包括WHERE子句时可能不会出现这种情况。 解决方法 由于这只使用了一个表,使用Tables(0)将避免拼写错误。检查Rows.Count也可以帮助:
If ds.Tables(0).Rows.Count > 0 Then
    txtID.Text = ds.Tables(0).Rows(0).Item(1)
    txtID.Name = ds.Tables(0).Rows(0).Item(2)
End If

Fill 是一个返回受影响的 行数 的函数,可以进行测试:

If da.Fill(ds, "Employees") > 0 Then...

示例3

Dim da As New OleDb.OleDbDataAdapter("SELECT TICKET.TICKET_NO,
        TICKET.CUSTOMER_ID, ... FROM TICKET_RESERVATION AS TICKET INNER JOIN
        FLIGHT_DETAILS AS FLIGHT ... WHERE [TICKET.TICKET_NO]= ...", con)
Dim ds As New DataSet
da.Fill(ds)

If ds.Tables("TICKET_RESERVATION").Rows.Count > 0 Then
DataAdapter会提供如前面示例中所示的TableNames,但它不会从SQL或数据库表中解析名称。因此,ds.Tables("TICKET_RESERVATION")引用了一个不存在的表。 解决方法是相同的,通过索引引用表格:
If ds.Tables(0).Rows.Count > 0 Then

另请参阅DataTable类


对象路径/嵌套

If myFoo.Bar.Items IsNot Nothing Then
   ...

该代码仅测试Items,而myFooBar可能都为Nothing。解决方法是逐个测试对象链或路径:

If (myFoo IsNot Nothing) AndAlso
    (myFoo.Bar IsNot Nothing) AndAlso
    (myFoo.Bar.Items IsNot Nothing) Then
    ....

AndAlso 非常重要。一旦遇到第一个 False 条件,后续测试将不会执行。这使得代码可以安全地一次又一次地 "钻取" 对象的 "级别",只有在确定 myFoo 有效之后(如果确实有效),才会评估 myFoo.Bar 。在编写复杂对象时,对象链或路径可能会变得非常长:

myBase.myNodes(3).Layer.SubLayer.Foo.Files.Add("somefilename")

无法引用任何“下游”null对象。这也适用于控件:

myWebBrowser.Document.GetElementById("formfld1").InnerText = "some value"

在这里,myWebBrowser或者Document可能是空的,或者formfld1元素可能不存在。


用户界面控件

Dim cmd5 As New SqlCommand("select Cartons, Pieces, Foobar " _
     & "FROM Invoice where invoice_no = '" & _
     Me.ComboBox5.SelectedItem.ToString.Trim & "' And category = '" & _
     Me.ListBox1.SelectedItem.ToString.Trim & "' And item_name = '" & _
     Me.ComboBox2.SelectedValue.ToString.Trim & "' And expiry_date = '" & _
     Me.expiry.Text & "'", con)

除了其他事情之外,这段代码没有预料到用户可能没有在一个或多个UI控件中选择任何内容。 ListBox1.SelectedItem 很可能是 Nothing,因此 ListBox1.SelectedItem.ToString 将导致 NRE。

解决方法

在使用数据之前进行验证(还要使用 Option Strict 和 SQL 参数):

Dim expiry As DateTime         ' for text date validation
If (ComboBox5.SelectedItems.Count > 0) AndAlso
    (ListBox1.SelectedItems.Count > 0) AndAlso
    (ComboBox2.SelectedItems.Count > 0) AndAlso
    (DateTime.TryParse(expiry.Text, expiry) Then

    '... do stuff
Else
    MessageBox.Show(...error message...)
End If

或者,您可以使用(ComboBox5.SelectedItem IsNot Nothing) AndAlso...


Visual Basic表单

Public Class Form1

    Private NameBoxes = New TextBox(5) {Controls("TextBox1"), _
                   Controls("TextBox2"), Controls("TextBox3"), _
                   Controls("TextBox4"), Controls("TextBox5"), _
                   Controls("TextBox6")}

    ' same thing in a different format:
    Private boxList As New List(Of TextBox) From {TextBox1, TextBox2, TextBox3 ...}

    ' Immediate NRE:
    Private somevar As String = Me.Controls("TextBox1").Text

这是一种获取NRE的相当常见的方法。在C#中,根据代码编写方式的不同,IDE将报告Controls不存在于当前上下文中,或者“无法引用非静态成员”。因此,在某种程度上,这是仅限于VB的情况。它也很复杂,因为它可能导致故障级联。
数组和集合无法通过这种方式进行初始化。这个初始化代码将在构造函数创建FormControls之前运行。结果是:
- 列表和集合将简单地为空 - 数组将包含五个元素的Nothing - somevar赋值将立即导致NRE,因为Nothing没有.Text属性
稍后引用数组元素将导致NRE。如果你在Form_Load中这样做,由于一个奇怪的错误,IDE在发生异常时可能不会报告异常。当你的代码尝试使用数组时,异常将弹出。这种“静默异常”在这篇文章中详细说明。对于我们的目的,关键是当创建表单(Sub NewForm Load事件)时发生灾难性事件时,异常可能不会报告,代码退出过程并仅显示表单。
由于在NRE之后,你的Sub NewForm Load事件中没有其他代码运行,因此许多其他事情可能会保持未初始化状态。
Sub Form_Load(..._
   '...
   Dim name As String = NameBoxes(2).Text        ' NRE
   ' ...
   ' More code (which will likely not be executed)
   ' ...
End Sub

注意:这适用于所有控件和组件引用,使它们在此处非法:

Public Class Form1

    Private myFiles() As String = Me.OpenFileDialog1.FileName & ...
    Private dbcon As String = OpenFileDialog1.FileName & ";Jet Oledb..."
    Private studentName As String = TextBox13.Text

部分解决方案

有趣的是,VB没有提供任何警告,但解决方法是在表单级别声明容器,但在表单加载事件处理程序中初始化它们,当控件存在时。只要您的代码位于InitializeComponent调用之后,在Sub New中也可以完成此操作:

' Module level declaration
Private NameBoxes as TextBox()
Private studentName As String

' Form Load, Form Shown or Sub New:
'
' Using the OP's approach (illegal using OPTION STRICT)
NameBoxes = New TextBox() {Me.Controls("TextBox1"), Me.Controls("TestBox2"), ...)
studentName = TextBox32.Text           ' For simple control references

数组代码可能还没有摆脱困境。任何在容器控件中的控件(如GroupBoxPanel)都不会在Me.Controls中找到;它们将在该面板或GroupBox的Controls集合中。当控件名称拼错时("TeStBox2"),也不会返回控件。在这种情况下,这些数组元素中再次存储Nothing,并且在尝试引用它时将导致NRE。

现在您知道要查找什么,这些应该很容易找到: VS shows you the error of your ways

"Button2"位于Panel

解决方法

与其使用表单的Controls集合进行间接名称引用,不如使用控件引用:

' Declaration
Private NameBoxes As TextBox()

' Initialization -  simple and easy to read, hard to botch:
NameBoxes = New TextBox() {TextBox1, TextBox2, ...)

' Initialize a List
NamesList = New List(Of TextBox)({TextBox1, TextBox2, TextBox3...})
' or
NamesList = New List(Of TextBox)
NamesList.AddRange({TextBox1, TextBox2, TextBox3...})

Function Returning Nothing

Private bars As New List(Of Bars)        ' Declared and created

Public Function BarList() As List(Of Bars)
    bars.Clear
    If someCondition Then
        For n As Integer = 0 to someValue
            bars.Add(GetBar(n))
        Next n
    Else
        Exit Function
    End If

    Return bars
End Function

这是一种情况,IDE会警告您'并非所有路径都返回值,可能会导致NullReferenceException'。您可以通过用Return Nothing替换Exit Function来抑制警告,但这并不能解决问题。当someCondition = False时,任何尝试使用返回值的东西都会导致NRE:

bList = myFoo.BarList()
For Each b As Bar in bList      ' EXCEPTION
      ...

解决方法

在函数中,将Exit Function替换为Return bList。返回一个空的List与返回Nothing是不同的。如果有可能返回一个Nothing对象,在使用之前进行测试:

 bList = myFoo.BarList()
 If bList IsNot Nothing Then...

Try/Catch实现不佳

Try/Catch的实现不佳会掩盖问题所在并导致新问题的出现:

Dim dr As SqlDataReader
Try
    Dim lnk As LinkButton = TryCast(sender, LinkButton)
    Dim gr As GridViewRow = DirectCast(lnk.NamingContainer, GridViewRow)
    Dim eid As String = GridView1.DataKeys(gr.RowIndex).Value.ToString()
    ViewState("username") = eid
    sqlQry = "select FirstName, Surname, DepartmentName, ExtensionName, jobTitle,
             Pager, mailaddress, from employees1 where username='" & eid & "'"
    If connection.State <> ConnectionState.Open Then
        connection.Open()
    End If
    command = New SqlCommand(sqlQry, connection)

    'More code fooing and barring

    dr = command.ExecuteReader()
    If dr.Read() Then
        lblFirstName.Text = Convert.ToString(dr("FirstName"))
        ...
    End If
    mpe.Show()
Catch

Finally
    command.Dispose()
    dr.Close()             ' <-- NRE
    connection.Close()
End Try

这是一个对象未按预期创建的案例,但也展示了空Catch的反作用。SQL中有一个额外的逗号(在“mailaddress”后面),导致在.ExecuteReader时出现异常。在Catch什么都不做后,Finally尝试进行清理,但由于您无法关闭null DataReader对象,因此会产生全新的NullReferenceException。
空的Catch块是魔鬼的游乐场。这位OP感到困惑的原因是他在Finally块中遇到NRE。在其他情况下,空的Catch可能会导致更远处的某些东西失控,并导致您花费时间在错误的地方查看问题。 (上述“静默异常”提供了相同的娱乐价值。)
解决办法:
不要使用空的Try / Catch块-让代码崩溃,这样您可以a)确定原因,b)确定位置并c)应用适当的解决方法。 Try / Catch块的目的不是隐藏异常,而是为了让开发人员唯一有资格修复它们的人看到它们。
DBNull与Nothing不同。
For Each row As DataGridViewRow In dgvPlanning.Rows
    If Not IsDBNull(row.Cells(0).Value) Then
        ...
IsDBNull函数用于测试一个是否等于System.DBNull来自MSDN:

System.DBNull的值表示对象代表缺失或不存在的数据。DBNull与Nothing不同,Nothing表示变量尚未初始化。

解决方法

If row.Cells(0) IsNot Nothing Then ...

与之前一样,您可以先测试是否为“Nothing”,然后再测试特定的值:

If (row.Cells(0) IsNot Nothing) AndAlso (IsDBNull(row.Cells(0).Value) = False) Then

示例2

Dim getFoo = (From f In dbContext.FooBars
               Where f.something = something
               Select f).FirstOrDefault

If Not IsDBNull(getFoo) Then
    If IsDBNull(getFoo.user_id) Then
        txtFirst.Text = getFoo.first_name
    Else
       ...

FirstOrDefault 返回第一个项目或默认值,对于引用类型是 Nothing,而不是 DBNull:

If getFoo IsNot Nothing Then...

控件

Dim chk As CheckBox

chk = CType(Me.Controls(chkName), CheckBox)
If chk.Checked Then
    Return chk
End If

如果无法找到名称为chkNameCheckBox(或者存在于GroupBox中),那么chk将为Nothing,尝试引用任何属性都将导致异常。

解决方案

If (chk IsNot Nothing) AndAlso (chk.Checked) Then ...

DataGridView

DataGridView(DGV)有一些周期性出现的怪癖:

dgvBooks.DataSource = loan.Books
dgvBooks.Columns("ISBN").Visible = True       ' NullReferenceException
dgvBooks.Columns("Title").DefaultCellStyle.Format = "C"
dgvBooks.Columns("Author").DefaultCellStyle.Format = "C"
dgvBooks.Columns("Price").DefaultCellStyle.Format = "C"

如果dgvBooksAutoGenerateColumns = True,它将创建列,但不会为它们命名,因此当引用名称时,上面的代码将失败。
解决方法:
手动命名列或按索引引用。
dgvBooks.Columns(0).Visible = True

示例2 — 小心NewRow

xlWorkSheet = xlWorkBook.Sheets("sheet1")

For i = 0 To myDGV.RowCount - 1
    For j = 0 To myDGV.ColumnCount - 1
        For k As Integer = 1 To myDGV.Columns.Count
            xlWorkSheet.Cells(1, k) = myDGV.Columns(k - 1).HeaderText
            xlWorkSheet.Cells(i + 2, j + 1) = myDGV(j, i).Value.ToString()
        Next
    Next
Next

当你的DataGridViewAllowUserToAddRowsTrue时(默认情况下),底部空/新行中的Cells都将包含Nothing。大多数尝试使用这些内容(例如ToString)都将导致NRE。
解决方法:
使用For / Each循环并测试IsNewRow属性,以确定是否为最后一行。无论AllowUserToAddRows是true还是false,此方法都适用:
For Each r As DataGridViewRow in myDGV.Rows
    If r.IsNewRow = False Then
         ' ok to use this row

如果您使用For n循环,请在IsNewRow为true时修改行计数或使用Exit For


My.Settings(StringCollection)

在某些情况下,尝试使用My.Settings中的StringCollection项可能会导致第一次使用时出现NullReference。解决方案相同,但不太明显。考虑以下内容:

My.Settings.FooBars.Add("ziggy")         ' foobars is a string collection

由于VB正在为您管理设置,因此可以合理地期望它初始化集合。它确实会这样做,但前提是您以前已经在集合中添加了初始条目(在设置编辑器中)。由于集合在添加项目时(显然)被初始化,因此当在设置编辑器中没有项目可添加时,它仍然保持为Nothing

解决方法

在需要时,在窗体的Load事件处理程序中初始化设置集合:

If My.Settings.FooBars Is Nothing Then
    My.Settings.FooBars = New System.Collections.Specialized.StringCollection
End If

通常情况下,Settings集合只需要在应用程序第一次运行时进行初始化。另一种解决方法是在项目 -> 设置 | FooBars中添加一个初始值,保存项目,然后删除虚假值。
要点:
你可能忘了使用New运算符。
或者
你认为可以完美地返回一个初始化对象到你的代码,但实际上没有。
永远不要忽略编译器警告,并始终使用Option Strict On
MSDN NullReference Exception

232

另一种情况是当你将一个null对象转换为值类型时。例如,下面的代码:

object o = null;
DateTime d = (DateTime)o;

进行强制类型转换时将抛出NullReferenceException异常。在上面的示例中,这似乎非常明显,但是在更多“后期绑定”复杂场景中可能会发生这种情况,其中空对象已从某些您不拥有的代码返回,并且类型转换由某些自动系统生成。

这种情况的一个例子是使用日历控件的简单ASP.NET绑定片段:

<asp:Calendar runat="server" SelectedDate="<%#Bind("Something")%>" />

这里,SelectedDate 实际上是 Calendar Web 控件类型的一个 DateTime 类型属性,并且绑定可以完全返回 null。ASP.NET 隐式生成器将创建一段代码,该代码将等同于上面的转换代码。这将引发一个非常难以发现的 NullReferenceException,因为它位于 ASP.NET 生成的代码中,而该代码可以编译通过...


7
太棒了。避免的一种简单方法是: DateTime x = (DateTime) o as DateTime? ?? defaultValue; - Serge Shultz

174

这意味着你的代码使用了一个被设置为null的对象引用变量(即它没有引用任何实际的对象实例)。

为了防止该错误发生,潜在可能为null的对象在使用之前应该进行null检查。

if (myvar != null)
{
    // Go ahead and use myvar
    myvar.property = ...
}
else
{
    // Whoops! myvar is null and cannot be used without first
    // assigning it to an instance reference
    // Attempting to use myvar here will result in NullReferenceException
}

170

这意味着变量指向了空值。可以像下面这样生成:

SqlConnection connection = null;
connection.Open();

那会抛出错误,因为虽然我已经声明了变量“connection”,但它没有指向任何东西。当我尝试调用成员“Open”时,没有引用来解析它,它将抛出该错误。

为避免此错误:

  1. 在尝试对对象进行任何操作之前,请始终初始化您的对象。
  2. 如果您不确定对象是否为空,请使用 object == null 进行检查。

JetBrains 的 ReSharper 工具将识别代码中潜在的空引用错误,并允许您添加空值检查。在我看来,这种错误是导致错误的主要来源。


3
JetBrains的Resharper工具会识别代码中每个可能存在空引用错误的地方。这是不正确的。我有一种解决方案,没有这种检测,但代码偶尔会导致异常。我怀疑当涉及到多线程时,他们无法检测到某些情况下的错误,但我无法进一步评论,因为我尚未确定我的错误位置。 - j riv
1
但是当使用HttpContext.Current.Responce.Clear()时,如何解决NullReferenceException的问题呢?以上任何一种解决方案都无法解决它。因为在创建HttpContext对象的同时,会出现一个错误:“重载分辨失败,因为没有可访问的'New'接受此数量的参数。” - Sunny Sandeep

103

请注意,无论情况如何,在.NET中造成问题的原因始终相同:

您正在尝试使用一个引用变量,其值为 Nothing/null。当引用变量的值为 Nothing/null 时,这意味着它实际上未持有对堆上存在的任何对象实例的引用。

要么您从未为变量分配过值,从未创建过指定给变量的值的实例,或者手动将变量设置为 Nothing/null,或者调用了一个将该变量设置为 Nothing/null 的函数。


94
这个异常被抛出的一个例子是:当您尝试检查某些东西时,它是空的。
例如:
string testString = null; //Because it doesn't have a value (i.e. it's null; "Length" cannot do what it needs to do)

if (testString.Length == 0) // Throws a nullreferenceexception
{
    //Do something
} 

当您尝试对未实例化的东西执行操作时,.NET运行时将抛出NullReferenceException异常,如上面的代码。

与ArgumentNullException相比,后者通常作为一项防御措施抛出,如果一个方法期望传递给它的值不是null,则会引发该异常。

更多信息请参见C# NullReferenceException and Null Parameter


93

C#8.0,2019:可空引用类型

C#8.0 引入了可空引用类型非可空引用类型。因此只有可空引用类型必须检查以避免出现NullReferenceException


如果您未初始化引用类型,并且想要设置或读取其属性之一,它将会抛出一个NullReferenceException

示例:

Person p = null;
p.Name = "Harry"; // NullReferenceException occurs here.

您可以通过检查变量是否不为空来避免这种情况:

Person p = null;
if (p!=null)
{
    p.Name = "Harry"; // Not going to run to this point
}
为了完全理解为什么会引发 NullReferenceException,了解 值类型 和[引用类型][3]的区别非常重要。
因此,如果你处理的是值类型,就不会出现 NullReferenceExceptions。但当处理引用类型时,需要保持警惕!
只有像名称所示的引用类型才能容纳引用或者指向“null”(或无)。而值类型始终包含一个值。 引用类型(必须检查):
  • dynamic
  • object
  • string
值类型(可以简单地忽略):
  • 数值类型
  • 整型
  • 浮点型
  • decimal
  • bool
  • 用户定义的结构体

6
因为问题是“什么是NullReferenceException”,所以值类型不相关。 - John Saunders
22
我不同意John Saunders的观点。作为一名软件开发人员,区分值类型和引用类型非常重要,否则人们最终会检查整数是否为空。 - Fabian Bigler
5
没错,只是在这个问题的背景下无关。 - John Saunders
4
谢谢提示。我稍作修改并在顶部添加了一个示例。我仍然认为提及引用类型和值类型是有用的。 - Fabian Bigler
5
我认为你没有添加任何其他答案中没有的内容,因为这个问题假定了一个引用类型。 - John Saunders

84

另一个可能发生NullReferenceExceptions的情况是(错误地)使用as运算符:

class Book {
    public string Name { get; set; }
}
class Car { }

Car mycar = new Car();
Book mybook = mycar as Book;   // Incompatible conversion --> mybook = null

Console.WriteLine(mybook.Name);   // NullReferenceException

在这里,“Book”和“Car”是不兼容的类型;一个“Car”无法转换/强制转换为一个“Book”。当此转换失败时,“as”返回“null”。在此之后使用“mybook”会导致“NullReferenceException”。
通常情况下,您应该使用强制转换或“as”,如下所示:
如果您期望类型转换始终成功(即您预先知道对象应该是什么),则应使用强制转换:
ComicBook cb = (ComicBook)specificBook;

如果您不确定类型,但是想尝试将其用作特定类型,则使用as
ComicBook cb = specificBook as ComicBook;
if (cb != null) {
   // ...
}

2
拆箱变量时,这种情况经常发生。我发现在事件处理程序中经常发生这种情况,尤其是在我更改了UI元素的类型但忘记更新代码后端之后。 - Brendan

75
你正在使用包含空值引用的对象,因此会发生空异常。在这个例子中,字符串值为null,当检查其长度时会发生异常。
示例:
string value = null;
if (value.Length == 0) // <-- Causes exception
{
    Console.WriteLine(value); // <-- Never reached
}

异常错误为:

未处理异常:

System.NullReferenceException:对象引用未设置实例的值。在Program.Main()处。


1
多么深奥!我从未考虑过“null”常量是一个引用值。这就是C#如何抽象出“NullPointer”的吗?因为我记得在C++中,可以通过解除初始化的指针(即C#中的ref类型)来引起NPE,其默认值恰好是未分配给该进程的地址(在许多情况下,这将是0,特别是在后来的C++版本中进行自动初始化,这属于操作系统 - 与之交战并死亡(或者只是捕获操作系统攻击您的进程的sigkill))。 - samus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接