zoukankan      html  css  js  c++  java
  • C++Explanation of ++val++ and ++*p++ in C

    今天本来想搜一下*p++的意思,结果发现一个比较有意思的帖子。

    http://stackoverflow.com/questions/3660048/explanation-of-val-and-p-in-c

    ASK:

    int val = 5;
    
    printf("%d",++val++); //gives compilation error : '++' needs l-value
    
    int *p = &val;
    printf("%d",++*p++); //no error

    Could someone explain these 2 cases? Thanks.

    ANSWER1:

    ++val++ is the same as ++(val++). Since the result of val++ is not an lvalue, this is illegal. And as Stephen Canon pointed out, if the result of val++ were an lvalue, ++(val++) would be undefined behavior as there is no sequence point between the ++s.

    ++*p++ is the same as ++(*(p++)). Since the result of *(p++) is an lvalue, this is legal.

    ANSWER2:

    Post-increment operator always returns an rvalue(in C as well as in C++). In C++, (++val)++ invokes Undefined Behavior because pre-increment(++) operator returns an lvalue(in C++) and the value of val is being modified more than once between two sequence points. – Prasoon Saurav

    直到今天才知道Sequence Point这个概念是在是罪过,随便BAIDU了一下:

    http://learn.akae.cn/media/ch16s03.html

    3. Side Effect与Sequence Point 请点评

    如果你只想规规矩矩地写代码,那么基本用不着看这一节。本节的内容基本上是钻牛角尖儿的,除了Short-circuit比较实用,其它写法都应该避免使用。但没办法,有时候不是你想钻牛角尖儿,而是有人逼你去钻牛角尖儿。这是我们的学员在找工作笔试时碰到的问题:

    int a=0;
    a = (++a)+(++a)+(++a)+(++a);

    据我了解,似乎很多公司都有出这种笔试题的恶趣味。答案应该是Undefined,我甚至有些怀疑出题人是否真的知道答案。下面我来解释为什么是Undefined。

    我们知道,调用一个函数可能产生Side Effect,使用某些运算符(++ -- = 复合赋值)也会产生Side Effect,如果一个表达式中隐含着多个Side Effect,究竟哪个先发生哪个后发生呢?C标准规定代码中的某些点是Sequence Point,当执行到一个Sequence Point时,在此之前的Side Effect必须全部作用完毕,在此之后的Side Effect必须一个都没发生。至于两个Sequence Point之间的多个Side Effect哪个先发生哪个后发生则没有规定,编译器可以任意选择各Side Effect的作用顺序。下面详细解释各种Sequence Point。

    1、调用一个函数时,在所有准备工作做完之后、函数调用开始之前是Sequence Point。比如调用foo(f(), g())时,foof()g()这三个表达式哪个先求值哪个后求值是Unspecified,但是必须都求值完了才能做最后的函数调用,所以f()g()的Side Effect按什么顺序发生不一定,但必定在这些Side Effect全部作用完之后才开始调用foo函数。

    2、条件运算符?:、逗号运算符、逻辑与&&、逻辑或||的第一个操作数求值之后是Sequence Point。我们刚讲过条件运算符和逗号运算符,条件运算符要根据表达式1的值是否为真决定下一步求表达式2还是表达式3的值,如果决定求表达式2的值,表达式3就不会被求值了,反之也一样,逗号运算符也是这样,表达式1求值结束才继续求表达式2的值。

    逻辑与和逻辑或早在第 3 节 “布尔代数”就讲了,但在初学阶段我一直回避它们的操作数求值顺序问题。这两个运算符和条件运算符类似,先求左操作数的值,然后根据这个值是否为真,右操作数可能被求值,也可能不被求值。比如例 8.5 “剪刀石头布”这个程序中的这几句:

    ret = scanf("%d", &man);
    if (ret != 1 || man < 0 || man > 2) {
    	printf("Invalid input!\n");
    	return 1;
    }

    其实可以写得更简单(类似于[K&R]的简洁风格):

    if (scanf("%d", &man) != 1 || man < 0 || man > 2) {
    	printf("Invalid input!\n");
    	return 1;
    }

    这个控制表达式的求值顺序是:先求scanf("%d", &man) != 1的值,如果scanf调用失败,则返回值不等于1成立,||运算有一个操作数为真则整个表达式为真,这时直接执行下一句printf,根本不会再去求man < 0man > 2的值;如果scanf调用成功,则读入的数保存在变量man中,并且返回值等于1,那么说它不等于1就不成立了,第一个||运算的左操作数为假,就会去求右操作数man < 0的值作为整个表达式的值,这时变量man的值正是scanf读上来的值,我们判断它是否在[0, 2]之间,如果man < 0不成立,则整个表达式scanf("%d", &man) != 1 || man < 0 的值为假,也就是第二个||运算的左操作数为假,所以最后求右操作数man > 2的值作为整个表达式的值。

    &&运算与此类似,a && b的计算过程是:首先求表达式a的值,如果a的值是假则整个表达式的值是假,不会再去求b的值;如果a的值是真,则下一步求b的值作为整个表达式的值。所以,a && b相当于“if a then b”,而a || b相当于“if not a then b”。这种特性称为Short-circuit,很多人喜欢利用Short-circuit特性简化代码。

    3、在一个完整的声明末尾是Sequence Point,所谓完整的声明是指这个声明不是另外一个声明的一部分。比如声明int a[10], b[20];,在a[10]末尾是Sequence Point,在b[20]末尾也是。

    4、在一个完整的表达式末尾是Sequence Point,所谓完整的表达式是指这个表达式不是另外一个表达式的一部分。所以如果有f(); g();这样两条语句,f()g()是两个完整的表达式,f()的Side Effect必定在g()之前发生。

    5、在库函数即将返回时是Sequence Point。这条规则似乎可以包含在上一条规则里面,因为函数返回时必然会结束掉一个完整的表达式。而事实上很多库函数是以宏定义的形式实现的(第 2.1 节 “函数式宏定义”),并不是真正的函数,所以才需要有这条规则。

    还有两种Sequence Point和某些C标准库函数的执行过程相关,此处从略,有兴趣的读者可参考[C99]的Annex C。

    现在可以分析一下本节开头的例子了。a = (++a)+(++a)+(++a)+(++a);的结果之所以是Undefined,因为在这个表达式中有五个Side Effect都在改变a的值,这些Side Effect按什么顺序发生不一定,只知道在整个表达式求值结束时一定都发生了。比如现在求第二个++a的值,这时第一个、第三个、第四个++a的Side Effect发生了没有,a的值被加过几次了,这些都不确定,所以第二个++a的值也不确定。这行代码用不同平台的不同编译器来编译结果是不同的,甚至在同一平台上用同一编译器的不同版本来编译也可能不同。

    写表达式应遵循的原则一:在两个Sequence Point之间,同一个变量的值只允许被改变一次。仅有这一条原则还不够,例如a[i++] = i;的变量i只改变了一次,但结果仍是Undefined,因为等号左边改i的值,等号右边读i的值,到底是先改还是先读?这个读写顺序是不确定的。但为什么i = i + 1;就没有歧义呢?虽然也是等号左边改i的值,等号右边读i的值,但你不读出i的值就没法计算i + 1,那拿什么去改i的值呢?所以这个读写顺序是确定的。写表达式应遵循的原则二:如果在两个Sequence Point之间既要读一个变量的值又要改它的值,只有在读写顺序确定的情况下才可以这么写

  • 相关阅读:
    ElasticSearch学习记录
    用java代码手动控制kafkaconsumer偏移量
    kafka0.9.0及0.10.0配置属性
    kafka常用命令
    kafka消费者客户端(0.9.0.1API)
    kafka入门教程链接
    编程内功
    bzoj3145:[Feyat cup 1.5]Str
    3 SpringBoot与微服务
    2 微服务存在的问题和解决方案
  • 原文地址:https://www.cnblogs.com/dracohan/p/3014639.html
Copyright © 2011-2022 走看看