热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

递增一个空指针是否定义良好?-Isincrementinganullpointerwell-defined?

Therearelotsofexamplesofundefinedunspecifiedbehaviorwhendoingpointerarithmetics-pointe

There are lots of examples of undefined/unspecified behavior when doing pointer arithmetics - pointers have to point inside the same array (or one past the end), or inside the same object, restrictions on when you can do comparisons/operations based on the above, etc.

在做指针运算时,有很多未定义/未指定行为的例子——指针必须指向同一个数组(或一个数组结束后)或同一个对象内,以及基于上述条件进行比较/操作的限制等等。

Is the following operation well-defined?

以下操作是否定义良好?

int* p = 0;
p++;

8 个解决方案

#1


37  

§5.2.6/1:

§5.2.6/1:

The value of the operand object is modified by adding 1 to it, unless the object is of type bool [..]

操作数对象的值通过向其添加1来修改,除非对象类型为bool [..]

And additive expressions involving pointers are defined in §5.7/5:

和添加剂包括指针表达式中定义§5.7/5:

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

如果指针操作数和结果点都指向同一数组对象的元素,或数组对象的最后一个元素之后的元素,则计算不产生溢出;否则,行为就没有定义。

#2


14  

There seems to be quite low understanding what "undefined behaviour" means.

对于“未定义的行为”意味着什么,似乎有很低的理解。

In C, C++, and related languages like Objective-C, there are four kinds of behaviour: There is behaviour defined by the language standard. There is implementation defined behaviour, which means the language standard explicitely says that the implementation must define the behaviour. There is unspecified behaviour, where the language standard says that several behaviours are possible. And there is undefined behaviour, where the language standard doesn't say anything about the result. Because the language standard doesn't say anything about the result, anything at all can happen with undefined behaviour.

在C、c++和Objective-C等相关语言中,有四种行为:一种是由语言标准定义的行为。有实现定义的行为,这意味着语言标准明确地说,实现必须定义行为。有一些未指明的行为,语言标准说有几种行为是可能的。还有一种行为是没有定义的,语言标准没有对结果做任何说明。因为语言标准没有对结果做任何说明,任何事情都可能发生在未定义的行为上。

Some people here assume that "undefined behaviour" means "something bad happens". That's wrong. It means "anything can happen", and that includes "something bad can happen", not "something bad must happen". In practice it means "nothing bad happens when you test your program, but as soon as it is shipped to a customer, all hell breaks loose". Since anything can happen, the compiler can actually assume that there is no undefined behaviour in your code - because either it is true, or it is false, in which case anything can happen, which means whatever happens because of the compiler's wrong assumption is still correct.

这里有些人认为“不确定的行为”意味着“坏事发生”。这是错误的。它的意思是“任何事情都有可能发生”,包括“坏事可能发生”,而不是“坏事必须发生”。在实践中,它意味着“当您测试您的程序时,没有什么糟糕的事情发生,但是一旦它被交付给客户,所有的麻烦就会发生”。由于任何事情都可能发生,编译器实际上可以假设在代码中没有未定义的行为——因为要么是真的,要么是假的,在这种情况下任何事情都可能发生,这意味着由于编译器的错误假设而发生的任何事情仍然是正确的。

Someone claimed that when p points to an array of 3 elements, and p + 4 is calculated, nothing bad will happen. Wrong. Here comes your optimising compiler. Say this is your code:

有人声称当p点到3个元素的数组时,p + 4是计算出来的,没有什么不好的事情发生。错了。接下来是优化编译器。这是你的代码:

int f (int x)
{
    int a [3], b [4];
    int* p = (x == 0 ? &a [0] : &b [0]);
    p + 4;
    return x == 0 ? 0 : 1000000 / x;
}

Evaluating p + 4 is undefined behaviour if p points to a [0], but not if it points to b [0]. The compiler is therefore allowed to assume that p points to b [0]. The compiler is therefore allowed to assume that x != 0, because x == 0 leads to undefined behaviour. The compiler is therefore allowed to remove the x == 0 check in the return statement and just return 1000000 / x. Which means your program crashes when you call f (0) instead of returning 0.

如果p指向[0],则评估p + 4是未定义的行为,但如果p指向b[0],则不是。因此,编译器可以假设p指向b[0]。因此,编译器可以假设x != 0,因为x = 0会导致未定义的行为。因此,编译器可以在return语句中删除x == 0检查,返回1000000 / x,这意味着当您调用f(0)而不是返回0时,程序崩溃。

Another assumption made was that if you increment a null pointer and then decrement it again, the result is again a null pointer. Wrong again. Apart from the possibility that incrementing a null pointer might just crash on some hardware, what about this: Since incrementing a null pointer is undefined behavour, the compiler checks whether a pointer is null and only increments the pointer if it isn't a null pointer, so p + 1 is again a null pointer. And normally it would do the same for the decrementing, but being a clever compiler it notices that p + 1 is always undefined behaviour if the result was a null pointer, therefore it can be assumed that p + 1 isn't a null pointer, therefore the null pointer check can be ommitted. Which means (p + 1) - 1 is not a null pointer if p was a null pointer.

另一个假设是,如果您增加一个空指针,然后再次递减它,那么结果就是一个空指针。又错了。除了增加一个空指针可能只是在一些硬件,这:由于增加一个空指针是未定义行为,编译器检查指针是否为空,只有增加如果不是一个空指针的指针,所以p + 1又是一个空指针。通常会做同样的递减,但作为一个聪明的编译器它注意到p + 1总是未定义的行为如果结果是一个空指针,因此,可以认为,p + 1不是一个空指针,因此空指针的检查可以查找。这意味着(p + 1) - 1不是空指针,如果p是空指针。

#3


13  

Operations on a pointer (like incrementing, adding, etc) are generally only valid if both the initial value of the pointer and the result point to elements of the same array (or to one past the last element). Otherwise the result is undefined. There are various clauses in the standard for the various operators saying this, including for incrementing and adding.

指针上的操作(如递增、添加等)通常只在指针的初始值和结果点同时指向相同数组的元素(或指向一个元素后的元素)时才有效。否则,结果是未定义的。在标准中有各种各样的子句,包括增加和增加。

(There are a couple of exceptions like adding zero to NULL or subtracting zero from NULL being valid, but that doesn't apply here).

(有一些例外,比如将零添加到NULL或从NULL中减去零是有效的,但这里不适用)。

A NULL pointer does not point at anything, so incrementing it gives undefined behaviour (the "otherwise" clause applies).

空指针不会指向任何东西,因此递增它会给出未定义的行为(使用“否则”子句)。

#4


1  

It turns out it's actually undefined. There are systems for which this is true

它实际上是没有定义的。有些系统是这样的

int *p = NULL;
if (*(int *)&p == 0xFFFF)

Therefore, ++p would trip the undefined overflow rule (turns out that sizeof(int *) == 2)). Pointers aren't guaranteed to be unsigned integers so the unsigned wrap rule doesn't apply.

因此,+p会触发未定义的溢出规则(结果是sizeof(int *) = 2)。指针不能保证是无符号整数,因此不适用无符号包装规则。

#5


0  

As said by Columbo it is UB. And from a language lawyer point of view this is the definitive answer.

正如科伦坡所说,它是UB。从语言律师的角度来看,这是最终的答案。

However all C++ compiler implementations I know will give same result :

但是,我知道的所有c++编译器实现都会得到相同的结果:

int *p = 0;
intptr_t ip = (intptr_t) p + 1;

cout <

gives 0, meaning that p has value 4 on a 32 bit implementation and 8 on a 64 bits one

给出0,意味着p在32位实现上有值4,在64位上有8。

Said differently :

不同的说:

int *p = 0;
intptr_t ip = (intptr_t) p; // well defined behaviour
ip += sizeof(int); // integer addition : well defined behaviour 
int *p2 = (int *) ip;      // formally UB
p++;               // formally UB
assert ( p2 == p) ;  // works on all major implementation

#6


0  

From ISO IEC 14882-2011 §5.2.6:

从ISO IEC 14882 - 2011§. 5.2.6:

The value of a postfix ++ expression is the value of its operand. [ Note: the value obtained is a copy of the original value —end note ] The operand shall be a modifiable lvalue. The type of the operand shall be an arithmetic type or a pointer to a complete object type.

后缀+表达式的值是其操作数的值。【注:所得值为原值-尾注的副本】操作数为可修改的lvalue。操作数的类型应该是算术类型或指向完整对象类型的指针。

Since a nullptr is a pointer to a complete object type. So I wouldn't see why this would be undefined behaviour.

因为nullptr是指向完整对象类型的指针。所以我不明白为什么这是不确定的行为。

As has been said before the same document also states in §5.2.6/1:

由于之前一直说同样的文档也在§5.2.6/1:

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

如果指针操作数和结果点都指向同一数组对象的元素,或数组对象的最后一个元素之后的元素,则计算不产生溢出;否则,行为就没有定义。

This expression seems a bit ambiguous. In my interpretation, the undefined part might very well be the evaluation of the object. And I think nobody would disagree with this being the case. However, pointer arithmetics seem to only require a complete object.

这个表达似乎有点模棱两可。在我的解释中,未定义的部分很可能是对对象的评价。我认为没有人会反对这种情况。然而,指针算术似乎只需要一个完整的对象。

Of course postfix [] operators and subtractions or multiplications on pointer to array objects are only well defined, if they in fact point to the same array. Mostly important because one might be tempted to think that 2 arrays defined in succession in 1 object, can be iterated over like they were a single array.

当然,后缀[]运算符和数组对象指针上的减法或乘法只有在它们指向同一个数组时才被很好地定义。最重要的是,人们可能会认为在一个对象中连续定义了两个数组,它们可以像单个数组一样进行迭代。

So my conclusion would be that the operation is well defined, but evaluation would not be.

所以我的结论是,这个操作定义得很好,但评估却不是。

#7


-1  

Back in the fun C days, if p was a pointer to something, p++ was effectively adding the size of p to the pointer value to make p point at the next something. If you set the pointer p to 0, then it stands to reason that p++ would still point it at the next thing by adding the size of p to it.

回到有趣的C时代,如果p是一个指向某个东西的指针,那么p+实际上是将p的大小添加到指针值,使p指向下一个东西。如果你将指针p设置为0,那么它说明了p++仍然会通过增加p的大小来指向下一个东西。

What's more, you could do things like add or subtract numbers from p to move it along through memory (p+4 would point at the 4th something past p.) These were good times that made sense. Depending on the compiler, you could go anywhere you wanted within your memory space. Programs ran fast, even on slow hardware because C just did what you told it to and crashed if you got too crazy/sloppy.

更重要的是,你可以做一些事情,比如从p中加减数字来移动它通过内存(p+4会指向p后的4点)这些都是有意义的好时光。根据编译器的不同,您可以在内存空间中任意移动。程序运行得很快,即使是在运行缓慢的硬件上,因为C只是按照你的要求运行,如果你太疯狂或太草率,它就会崩溃。

So the real answer is that setting a pointer to 0 is well-defined and incrementing a pointer is well-defined. Any other constraints are placed on you by compiler builders, os developers and hardware designers.

所以真正的答案是,将指针设置为0是有定义的,而增加指针是有定义的。编译器构建程序、操作系统开发人员和硬件设计人员会对您施加任何其他约束。

#8


-2  

Given that you can increment any pointer of a well-defined size (so anything that isn't a void pointer), and the value of any pointer is just an address (there's no special handling for NULL pointers once they exist), I suppose there's no reason why an incremented null pointer wouldn't (uselessly) point to the 'one after NULL'est item.

考虑到你可以增加任何指针的定义良好的大小(因此,任何不是一个空指针),和任何指针的值只是一个地址(没有特殊处理NULL指针一旦存在),我想没有理由一个空指针不会增加(无用地)指向一个空是项。

Consider this:

考虑一下:

// These functions are horrible, but they do return the 'next'
// and 'prev' items of an int array if you pass in a pointer to a cell.
int *get_next(int *p) { return p+1; }
int *get_prev(int *p) { return p-1; }

int *j = 0;

int *also_j = get_prev(get_next(j));

also_j has had maths done to it, but it's equal to j so it's a null pointer.

also_j做过数学运算,但它等于j,所以它是一个空指针。

Therefore, I would suggest that's it's well-defined, just useless.

因此,我认为它定义得很明确,毫无用处。

(And the null pointer appearing to have the value zero when printfed is irrelevant. The value of the null pointer is platform dependent. The use of a zero in the language to initialise pointer variables is a language definition.)

(当printfed不相关时,空指针显示为零。空指针的值与平台相关。在语言中使用0初始化指针变量是一种语言定义。


推荐阅读
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • JavaScript中的数组是数据集合的核心结构之一,内置了多种实用的方法。掌握这些方法不仅能提高开发效率,还能显著提升代码的质量和可读性。本文将详细介绍数组的创建方式及常见操作方法。 ... [详细]
  • 本文探讨了如何在Classic ASP中实现与PHP的hash_hmac('SHA256', $message, pack('H*', $secret))函数等效的哈希生成方法。通过分析不同实现方式及其产生的差异,提供了一种使用Microsoft .NET Framework的解决方案。 ... [详细]
  • 主调|大侠_重温C++ ... [详细]
  • 本文将详细探讨 Java 中提供的不可变集合(如 `Collections.unmodifiableXXX`)和同步集合(如 `Collections.synchronizedXXX`)的实现原理及使用方法,帮助开发者更好地理解和应用这些工具。 ... [详细]
  • 开发笔记:由数据库某字段存数组引发的json_encode/serialize思考
    开发笔记:由数据库某字段存数组引发的json_encode/serialize思考 ... [详细]
  • 最近同事提了一个需求过来,他觉得项目对于第三方日志记录的太多了,只想记录一些业务相关的日志减少对于框架日志的显示。具体要求就是对于框架日志只显示warn等级以上的,而业务日志显示info等级以上 ... [详细]
  • 本章详细介绍SP框架中的数据操作方法,包括数据查找、记录查询、新增、删除、更新、计数及字段增减等核心功能。通过具体示例和详细解析,帮助开发者更好地理解和使用这些方法。 ... [详细]
  • 由二叉树到贪心算法
    二叉树很重要树是数据结构中的重中之重,尤其以各类二叉树为学习的难点。单就面试而言,在 ... [详细]
  • Java多线程实现:从1到100分段求和并汇总结果
    本文介绍如何使用Java编写一个程序,通过10个线程分别计算不同区间的和,并最终汇总所有线程的结果。每个线程负责计算一段连续的整数之和,最后将所有线程的结果相加。 ... [详细]
  • 本文探讨了符号三角形问题,该问题涉及由相同数量的“+”和“-”符号组成的三角形。通过递归回溯法,可以有效地搜索并计算符合条件的符号三角形的数量。 ... [详细]
  • 本文将继续探讨前端开发中常见的算法问题,重点介绍如何将多维数组转换为一维数组以及验证字符串中的括号是否成对出现。通过多种实现方法的解析,帮助开发者更好地理解和掌握这些技巧。 ... [详细]
  • 深入剖析JVM垃圾回收机制
    本文详细探讨了Java虚拟机(JVM)中的垃圾回收机制,包括其意义、对象判定方法、引用类型、常见垃圾收集算法以及各种垃圾收集器的特点和工作原理。通过理解这些内容,开发人员可以更好地优化内存管理和程序性能。 ... [详细]
  • 本文详细探讨了Java中的ClassLoader类加载器的工作原理,包括其如何将class文件加载至JVM中,以及JVM启动时的动态加载策略。文章还介绍了JVM内置的三种类加载器及其工作方式,并解释了类加载器的继承关系和双亲委托机制。 ... [详细]
  • 本文介绍了如何在iOS应用中自定义导航栏按钮,包括使用普通按钮和图片生成导航条专用按钮的方法。同时,探讨了在不同版本的iOS系统中实现多按钮布局的技术方案。 ... [详细]
author-avatar
8023pxeb_256
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有