C和c++在++操作符方面的区别
The difference between C and C++ regarding the ++ operator
我一直在摆弄一些代码,看到一些我不明白"为什么"的东西。
int i = 6;
int j;
int *ptr = &i;
int *ptr1 = &j
j = i++;
//now j == 6 and i == 7. Straightforward.
如果将运算符放在等号的左侧会怎样?
++ptr = ptr1;
等价于
(ptr = ptr + 1) = ptr1;
与
ptr++ = ptr1;
等价于
ptr = ptr + 1 = ptr1;
后缀运行编译错误,我得到它。在赋值操作符的左边有一个常数ptr + 1。很好。
前缀1在c++中编译和工作。是的,我知道这很混乱,而且您正在处理未分配的内存,但它可以工作并编译。在C语言中,这不会编译,返回与后缀"左值要求作为赋值的左操作数"相同的错误。无论如何编写,使用两个"="操作符展开或使用"++ptr"语法展开,都会发生这种情况。
C处理这种赋值的方式和c++处理它的方式有什么不同?
在C和c++中,x++
的结果是一个右值,所以你不能给它赋值。
在C中,++x
等于x += 1
(C标准§6.5.3.1/p2;所有C标准均为WG14 N1570)。在c++中,如果x
不是bool
,则++x
相当于x += 1
(c++标准§5.3.2 [expr.pre.incr]/p1;所有c++标准引用均符合WG21 N3936)。
在C中,赋值表达式的结果是一个右值(C标准§6.5.16/p3):
赋值操作符将值存储在操作对象指定的对象中左操作数。赋值表达式具有左侧的值赋值后的操作数,但不是左值。
因为它不是左值,所以你不能给它赋值:(C standard§6.5.16/p2 -注意这是一个约束)
赋值操作符的左端必须有一个可修改的左值操作数。
在c++中,赋值表达式的结果是左值(c++标准§5.17 [expr.ass]/p1):
赋值操作符(=)和复合赋值操作符all从右到左。它们都需要一个可修改的左值作为左值操作数,并返回指向左操作数的左值。
所以++ptr = ptr1;
在C中违反了可诊断的约束,但在c++中没有违反任何可诊断的规则。
然而,在c++ 11之前,++ptr = ptr1;
具有未定义的行为,因为它在相邻的两个序列点之间修改了ptr
两次。
在c++ 11中,++ptr = ptr1
的行为得到了很好的定义。写成
(ptr += 1) = ptr1;
从c++ 11开始,c++标准规定(§5.17 [expr.ass]/p1)
在所有情况下,赋值都是在值计算之后排序的在左右操作数的值计算之前赋值表达式。相对于不确定顺序的函数调用,复合操作赋值是一次求值。
因此,=
的赋值是在ptr += 1
和ptr1
的值计算之后进行排序的。+=
执行的赋值在ptr += 1
的值计算之前进行排序,+=
需要的所有值计算都必须在该赋值之前进行排序。因此,这里的顺序是定义良好的,没有未定义的行为。
在C语言中,前后递增的结果都是右值,我们不能赋值给右值,我们需要一个左值(也参见:理解C和c++中的左值和右值)。我们可以通过C11标准草案6.5.2.4
后缀自增和自减运算符看到(强调向前):
结果后缀++操作符的值的操作数。[…参见关于加法运算符和化合物的讨论约束、类型和转换信息的赋值指针操作的效果。[…]
所以后增的结果是一个值,它是右值的同义词,我们可以通过6.5.16
赋值操作符一节来确认这一点,上面的段落指出,为了进一步理解约束和结果,它说:
[…赋值表达式的左操作数的值在赋值,,但不是左值。[…]
进一步确认后增运算的结果不是左值。
对于预增量,我们可以从6.5.3.1
部分看到前缀自增和自减操作符,其中表示:
[…参见关于加法运算符和复合赋值的讨论有关约束、类型、副作用和转换的信息指针操作的影响。
也像后增量一样指向6.5.16
,因此C中预增量的结果也不是左值。
在c++中后增量也是右值,更具体地说是右值,我们可以通过5.2.6
自增和自减一节来确认这一点,该节说:
[…结果为右值。结果的类型为cv-不合格操作数类型的版本[…]
对于预增量C和c++是不同的。在C中,结果是右值,而在c++中,结果是左值,这解释了为什么++ptr = ptr1;
在c++中工作而不是C。
对于c++,这在5.3.2
递增和递减一节中介绍,其中说:
[…结果是更新后的操作数;是左值,它是如果操作数是位域,则为位域。[…]
了解是否:
++ptr = ptr1;
在c++中是否定义良好,我们需要两种不同的方法,一种用于c++ 11之前,另一种用于c++ 11。
c++ 11之前,此表达式调用未定义行为,因为它在同一序列点内多次修改对象。我们可以在c++ 11之前的标准草案5
Expressions中看到这一点,其中说:
除特别说明外,单个操作数的求值顺序单个表达式的操作符和子表达式,以及顺序57):通过表达式求值最多修改一次的值。而且,访问先验值只能是为了确定要存储的值。应满足本款的要求的子表达式的每个允许排序的表情;否则行为是未定义的。(例子:
i = v[i ++]; / / the behavior is undefined i = 7 , i++ , i ++; / / i becomes 9 i = ++ i + 1; / / the behavior is undefined i = i + 1; / / the value of i is incremented
-end example]
我们对ptr
进行递增,然后对其赋值,这是两次修改,在这种情况下,序列点出现在表达式末尾的;
之后。
对于C+11,我们应该转到缺陷报告637:排序规则和示例不一致,哪个缺陷报告导致:
i = ++i + 1;
在c++ 11中成为定义良好的行为,而在c++ 11之前,这是未定义的行为。这个报告中的解释是我见过的最好的解释之一,读了很多遍很有启发,帮助我从新的角度理解了很多概念。
导致这个表达式成为定义良好的行为的逻辑如下:
赋值副作用需要在LHS和RHS (5.17 [expr。[a]第1段。
LHS (i)是一个左值,所以计算它的值需要计算i的地址。
为了对RHS (++i + 1)进行值计算,必须首先对左值表达式++i进行值计算,然后对结果进行左值到右值的转换。这保证了在计算加法操作之前对递增的副作用进行排序,而加法操作又在赋值操作之前对其进行排序。换句话说,它为这个表达式生成一个定义良好的顺序和最终值。
下面的逻辑有点类似:
++ptr = ptr1;
LHS和RHS的值计算在赋值副作用之前排序。
RHS为左值,因此计算RHS值需要计算ptr1的地址。
为了对LHS (++ptr)进行值计算,必须首先对左值表达式++ptr进行值计算,然后对结果进行左值到右值的转换。这保证了在赋值副作用之前对递增副作用进行排序。换句话说,它为这个表达式生成一个定义良好的顺序和最终值。
注意
OP说:
对于加法运算符,指向非数组对象的指针被认为是大小为1的数组,我将引用c++标准草案,但C11有几乎完全相同的文本。来自是的,我知道它很乱,你正在处理未分配的内存,但它可以工作并编译。
5.7
加性运算符:
对于这些操作符,指向非数组对象的指针的数组第一个元素的指针的行为相同长度为1,以对象的类型作为元素类型
,并进一步告诉我们,只要不解除对指针的引用,指向数组末尾后面的指针是有效的:
[…如果操作数指针和结果都指向相同的数组对象,或数组的最后一个元素的后面,评价不得产生溢流;否则,行为未定义
:
++ptr ;
仍然是一个有效指针。
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- int(c) 和 c-'0' 之间的区别。C++
- 向量 <int> a {N, 0} 和 int arr a[N] = {0} 的时间复杂度有什么区别
- C++ - "!pointer"和"pointer == nullptr"的区别?
- C++ 使用 assign 函数的字符串与直接使用 '=' 更改值的字符串之间的区别
- printf() 和 std::cout 在指针方面的区别
- 如果我提前将参数声明为变量而不是将它们内联写入函数调用,那有什么区别(在内存方面)?
- c++ 类中的静态常量变量和常量变量在存储方面是否有区别
- 在内存使用方面,c++ 中的 map 和 unordered_map 之间有什么区别吗?
- 在密钥扩展和再生成方面,AES128 和 AES256 与 AES128 和 AES256 有什么区别?(也许还有 AE
- 在C++中,a+i 和 &a[i] 在指针算术方面有什么区别?
- MFC和Windows API之间的区别在获取屏幕分辨率方面
- Turbo C++7和Dev C++在语法方面有什么区别
- 除了语法,"call by reference"和"call by pointer"在内存方面C++有什么区别吗?
- 在记忆方面"hard-coding"和传递论点有什么区别?
- C和c++在++操作符方面的区别
- c++中静态变量和全局变量在内存管理方面的区别
- 在使用内存方面,用户定义的堆栈和内置的堆栈有什么区别?
- 这两者在位操作方面有什么区别?
- SDL_HWSURFACE和SDL_SWSURFACE在速度和性能方面有什么区别吗?