【发布时间】:2023-04-02 20:48:01
【问题描述】:
关于 C 指针here 的面试问题之一如下:什么是空指针赋值错误?
我用谷歌搜索了一段时间,没有看到任何合理的解释。那是什么?试图通过空指针写入?特定于架构或环境的东西?究竟是什么错误?
【问题讨论】:
关于 C 指针here 的面试问题之一如下:什么是空指针赋值错误?
我用谷歌搜索了一段时间,没有看到任何合理的解释。那是什么?试图通过空指针写入?特定于架构或环境的东西?究竟是什么错误?
【问题讨论】:
http://www.faqs.org/qa/qa-3786.html
空指针赋值是运行时错误 它的发生是由于各种原因,一个是您的程序试图访问非法的内存位置。 非法位置意味着该位置要么在操作系统地址空间中,要么在其他进程的内存空间中。 在 stdio.h 中,NULL 定义为 0 因此,当您的程序尝试访问第 0 个位置时,操作系统会因运行时分配错误而终止您的程序,因为第 0 个位置位于操作系统地址空间中,并且操作系统不允许用户程序访问其地址空间。
示例代码:
int* ptr = NULL;
*ptr = 3;
说明:
在几乎每个系统上,地址 0 都是保留的。系统不允许您写入该位置。如果你尝试,你会得到一个运行时异常(访问冲突、segmentation fault 等)。
【讨论】:
其实我记不起源头了,但是根据源头,这个运行时错误仅限于相应编译器正在使用的中小型内存模型。你看,如前所述,空指针实际上并不指向零,实际上不同的编译器使用不同但固定的内存位置作为空指针。
让我们考虑 TC 编译器的情况,该编译器在数据段和 TC 版权声明的底部放置了四个零字节。 TC 也使用 DS:0000 位置,数据段的底部作为空指针位置。所以,给这个空指针赋值,实际上会改变四个字节,并且可能会弄乱版权声明。
现在,在程序终止时,会检查四个零和版权标语是否有任何更改。如果发现任何更改,则会生成空指针分配错误。
所以,我认为它不仅仅是空指针,任何变得狂野的指针,如果试图访问某些关键区域,你会遇到空指针分配错误。
【讨论】:
在很多情况下您都可以看到问题。但关键是,您没有正确分配内存。运行程序后,以下代码将产生空指针赋值错误消息。注意:它会正确编译。
void CopyMessage(char *p)
{
strcpy(p, "welcome");
}
void main()
{
char *src;
CopyMessage(src);
}
【讨论】:
当你试图指向非法内存空间时发生运行时错误,通常是为操作系统保留的地址 0。
【讨论】:
我在这个答案中的意图是对空指针最基本概念的补充。这个最简单的定义,正如在许多地方列出的那样,每当指向地址的基值被分配一个“0”或 NULL 值时,因为零页,第 0 个内存位置是操作系统地址空间的一部分并且操作系统不允许用户程序对其地址空间的访问。在这种情况下,预编译器或编译器可能会产生错误,或者该错误可能由操作系统本身在运行时作为内存访问冲突产生。
以下对空指针的讨论基于编程中包含的概念,这些概念发生在允许精细控制并需要了解如何处理变量空间的语言的机器级别。
大多数高级语言和编译器可能会通过适当的“类型”强制转换、指定选项库和在索引中不进行心理错误计算来防止这种情况发生。 “C”作为一种语言,在没有指定最严格的编译器参数的情况下,特别容易出现这些类型的错误,以及今天在“袖珍”处理器中发现的不太复杂的基于机器的编译器或编程语言。
然而,自从计算机和编程语言问世以来,零指针错误的概念扩展为包括指向任何受保护内存位置中第 0 位置的任何指针。但尤其是在可用于指向任何内存位置的内存位置如何被无意覆盖以包含空值的上下文中。在这里,我研究了这个概念,因为我称之为“1 的错误”,当程序员必须在选项基数“0”或选项基数“1”之间切换时会发生这种情况。这是一个计数问题,我们以“0”或“1”开始计数,如下所示:
Option Base 0
[0,1,2,..,9] or
Option Base 1
[1,2,3,...,10]
对于一个有 10 个元素的数组。错误 1 可能会导致计算错误,从而导致指向数组“之前”的第一个内存位置的指针,
Option Base 1
0[1,2,3,...,10]
^
|Last memory location of another variable space 'before' this variable space
或数组“之后”的第一个位置,根据定义超出范围。
Option Base 0
[0,1,2,...,9]10
^
|First memory location of another variable after this variable
但是,在处理使用直接内存访问的编程时,就像在任何语言的原始机器代码中一样,错误 1 可能是悲剧性的,将非预期值放置在预期范围之外的内存位置,在变量的情况下space 和 using pointers 是预期变量之前或之后的变量空间,当数组被初始化或清除时,会在不需要的位置创建一个“null”或 0,尤其是如果它是一个指针数组,则会在意外中出现空指针错误变量。
这当然取决于变量空间的结构和/或什么类型。如果变量或其他存储地址空间嵌套在代码中,可能会特别麻烦。正如我之前所说,许多高级语言编译器可以规避大多数此类错误。但是,当在机器代码中编写特定的子例程时,无论出于何种原因认为有必要,都必须格外小心,以确保选项库被明确定义并在实践中得到遵守,如果不是编译器约定的话。
首先,程序员认识到明确定义程序和存储区域的必要性,未经明确同意,任何内容都不得修改哪怕是一点点数据。这对于空指针至关重要,因为操作系统的零页区域中的第 0 个内存位置通常用于存储堆栈,这些堆栈是压入堆栈以进行返回操作的内存位置。系统调用是否由于可屏蔽或不可屏蔽中断,或者因为程序员想要推送数据或内存位置而推送返回操作的地址(从系统中断的地方弹出返回地址)稍后从该堆栈中弹出。这是一个保护区。像任何指向有效内存地址的指针一样,人们不想写入错误的位置,如果被覆盖,第 0 位置特别容易受到影响,因为变量通常为空或从初始上电状态具有 0 值,因此变量上电后未明确定义,或有意初始化的可能为零。
对于零页上的堆栈,或任何包含返回地址的堆栈,如果值被压入堆栈,并且在遇到'return'之前没有弹出,则返回值可以为空或零,并且返回指向堆栈的内存位置。这是一个空指针错误,它可能不会产生错误,而是将代码指针返回到不包含代码的区域,例如堆栈的中间。这些漏洞利用是众所周知的,并且经常被用于破坏系统安全性以获取不那么谨慎的破解者的访问权限;或可用于特殊情况下的巧妙取用,或意外制造各种来源难以确定的恶作剧。
正如我所说,这种描述超出了空指针错误的传统定义,但它仍然可以产生空指针,尽管更常见的是产生其他错误,或者根本不产生错误。除了“如果或何时”程序未能按预期执行之外,它通常不会表明它的存在。
在这里,我提供了额外的非常规示例和空指针分配错误的潜在来源的定义,而不是定义传统的理解,这在编程约定中更多的是错误而不是编程逻辑中的错误。
这种类型的错误(未定义或 null)非常少见。但是,使用 Arduino、Raspberry PI、AMD 等台式设备或任何其他片上计算机编程的“袖珍”处理器的现代编程以无数种形式存在,其中许多在今天和过去一样简单,这个空指针问题今天仍然存在,甚至在最复杂的系统中也可能发生。此外,构建自己的变量或数据结构的公司可能也是当今最有可能看到这种类型错误的人。目的是展示有助于识别的示例。
正如以前定义的那样,很快就认识到产生空指针错误的条件也可能产生指针值被无意修改的错误。然后,作为一个变量,它被用作指针并且在没有程序员的意图或知识的情况下被覆盖,它可以是空值,但也可以是任何其他值。所以,我们发现可以创建空指针的问题,也可以创建非空指针。空指针是一种特殊情况,它经常会产生系统错误消息;但是,当这些相同的条件导致指针采用随机或未确定的值而不是数据应驻留的原始地址时,它现在包含一个空地址或未知地址,这可能导致将数据移动或存储到无效或不需要的位置覆盖和破坏该代码或数据。
大多数人会理所当然地认为 this 是 NOT 空指针错误;而且,它们是完全 100% 正确!然而,这个错误的根源通常会产生奇怪的经常看到的空指针错误,因为更多时候指针将包含一个“空”!这个定义练习的重点是指出空指针的创建也可能导致一个问题,该问题似乎没有表明问题的原始源。 IOW,在概念上没有指向问题的指针。由于与创建奇怪的“空”指针问题的关系,并且在这种情况下,由于指针为 NOT 为空,而是“未定义”,因此随后缺少指向错误源的数据' 从自上而下的编程过渡到面向对象的事件驱动编程的老前辈们认识到了这种关系,并认识到这种似乎没有可定义来源的“空”指向错误。
由于这种类型的故障,损坏的数据或损坏的代码在移动到现有未使用的内存位置时可能不会立即执行或使用。但是,当代码或数据在运行的稍后时间确实导致问题时,没有关于错误“真实”位置的信息,因为它与导致它的事件相距甚远。无论是创建或分配空指针还是创建其他一些损坏,它都会修改代码,并且事情会变得很奇怪,真的很奇怪。
总而言之,我将空指针定义为用于指向内存位置的任何空地址或未定义地址,而不管最初创建它的是什么。可以将空指针分配错误或许多其他错误分配给此问题和示例。
在更简单的架构或编程环境中,它可以引用任何无意中最终创建空值作为指针的代码,或者创建无论如何都会停止执行的错误,例如覆盖返回堆栈中的一个字节,覆盖代码,代码不小心将“0”存储在错误的位置、现有代码中,或者只是作为数据存储在错误的位置,而不仅仅是作为地址。
因此,虽然上面的示例可以很好地定义空指针的示例。所以我们扩展了这个概念,空指针是任何被用作变量指针的指针,并且由于多种原因中的任何一个,该变量的地址位置现在包含一个“空”或 ANY 非预期值这导致它指向一个不受欢迎的内存位置,无论它是如何到达那里的,而不仅仅是编程逻辑错误或数学计算错误。 IOW,不仅仅是指针中的 0;更具体地说,在任何内存位置中的零值或未定义值 该内存位置不是特定目标,并且在其他情况下它现在将执行的其他目的!
所以,最后,我们会得到一个空指针错误,并且在检查指针时发现它包含一个空值;但是,找不到将 null 放入指针或分配它的代码。这是空指针分配错误的最广泛定义,并且绝对是空指针错误的最坏情况。当这种情况发生在大型程序中时,通常会导致程序死亡,因为如果错误存在于以前的版本中,但正在写入以前可访问但未分配的意外内存位置(允许程序运行或 IOW)较早的版本,在程序扩展之前不会注意到错误,现在以前未使用的内存位置包含新代码或数据,这允许旧错误现在在新代码中生成随机错误或损坏数据!
例如:在较早的版本中,错误的地址值会导致数据被写入定义的变量空间之外,但在几个版本中却没有被注意到,因为正在写入数据,正在读取数据,以及程序和所有内容'出现'好的!但是,随着程序的扩展,新代码现在相对地存在于与原始旧错误错误地写入错误内存位置的内存相同的相对地址空间中,并且没有人注意到,无论是一个字节还是整个数据块!但是,现在,那里存在新代码。当程序在今天或明天运行该特定代码时,无论调用包含它的任何函数,新数据都会被未发现的旧错误破坏。
找到一年前存在的“原始”错误现在几乎(即使不是完全)也无法找到。
管理员和开发人员的逻辑通常规定,我为什么要查看那里,我们知道代码在最后几个版本中运行良好。但是,现在,部分新代码不起作用,主要部分已损坏。我们看了又看,一无所获。就好像错误不存在,但它确实存在。是什么原因造成的,谁怀疑几年前编写的代码?空指针和许多其他错误也是由此引起的。有了理解,以及可以直接检查代码的良好编辑器,适当的监视器可以观察触发暂停的修改内存位置,以确定在正确的时间执行的代码,即使这也可以找到。
【讨论】: