【C语言】优先级,结合方向和执行顺序

1. 问题的提出

K&R的书中一再强调"C is not a big language",当时看书的时候无法理解这句话的意思。现在我的理解是C标准本身的限制比较小,留给程序员的空间较大。这样一来C中的而有些问题标准就没有给出限定,就会产生一些让人迷惑的地方。今天我遇到了一个这样的问题,解决这些问题有的时候看起来是“钻牛角尖”,好吧,我就是一个爱钻牛角尖的人。这里需要解决的问题是执行顺序的问题,先给出几个问题。

int i = 0, j = 0;
j = i++ + i++ + i++;

执行完这两个语句后i和j的值各是什么?(看着是不是很熟悉,上次笔试考C中是不是有这个呢)

int i=0;
int arry[10];
arry[i] = i++;

执行完上述语句后,i和数组arry中的值是多少?

#define PRINT(x, y, z)  printf(" x = %d, y = %d, z = %d\n", (x), (y),(z))
x = y = z = 1;
++x || ++y && ++z; PRINT(x, y, z);     

输出结果是什么?(来自于C Puzzle Book Operators 1.6 )
先把这几个问题放在这里,先思考下,下面将会解决他们。

2. 明确两个概念:side effects, sequence point

2.1 side effects

C语言经典著作 “The C Programming Language” 中对于side effects的定义:

Function calls, nested assignment statements, and increment anddecrement
operators cause ‘side effects’ – some variable is changed as a by-product
of the evaluation of an expression.

这里述说的side effect可以理解为一种“副作用”,这种作用是改变一个变量的值。
“C In Nutshell” 中关于side effects的定义:

In addition to yielding a value, the evaluation of an expression can result in other changes in the execution environment, called side effects. Examples of such changes include modifications of a variable's value, or of input or output streams.

相对于K&R中的定义这里使用了对于环境的改变,这应该更加准确。总结:side effects 就是程序中的实体产生的改变,这里所说的实体通常指变量。
赋值,自增,自减表达式会产生side effects,函数调用表达式也有可能产生side effects。

2.2 sequence points

sequence points: A sequence point is a point in time at which the dust has settled and all side effects which have been seen so far are guaranteed to be complete. The sequence points listed in the C standard are:

  1. at the end of the evaluation of full expression ( a full expression is an expression statement, or any other expression which is not subexpression within any large expression);
  2. at the ||, &&, ?:,and comma operators; and
  3. at a function call (after the evaluation of all arguments, and just before the actual call).

序列点(sequence points)是一种逻辑意义的点,它的意义在于,逻辑点前的副作用(side effects)都在这时生效。C标准中定义的序列点总共有三类,第一类是完全表达式(full expression);第二类是||,&&,?:和;第三类是函数调用,在所有的参数确定后、函数真正调用之前。

2.3 side effects 和sequence points对于编写程序有什么意义?

2.3.1我的“SS1”和“SS2”原则

标准中规定了在前一个序列点前的副作用都会在前一个序列点后完成,但是标准没有规定两个序列点之间的副作用生效的顺序,不同的C语言实现的顺序可能不同。请注意这一点,这是所有问题产生的根本原因。如果两个序列点之间有超过两个的副作用作用在同一个实体上,这样不同的编译器产生的结果就不同,这种情况在标准中称为unspecified 。所以在实际应用中应该避免这种情况的出现,我把这一个原则称为为SS1。

是不是遵守了SS1原则就不会产生unspecified了呢?非也。可以设想这样一种情况:每一个实体(A)在两个序列点之间被两次使用,只有一次对这个实体本身产生副作用,另外一次被间接的用来产生副作用作用于另外一个实体(B)。在前面设想的这种情况下虽然符合SS1原则,但是我们会发现被间接用来产生副作用时,对于实体(B)产生的副作用肯定会跟实体(A)有关,但是这个实体(A)在这个序列点区间中有被副作用作用,那么我们就无法确定这个实体(A)的值了,从而实体(B)也就无法确定了。这里可以归纳为:在两个序列点之间,如果出现对一个实体的多次引用,并且只有一次会对该实体产生副作用(SS1),那么所有的这些引用都必须用来产生这个副作用 ,我把这一个原则称为SS2。只有同时遵守了SS1和SS2,写出的表达式才不是unspecified类型的。

2.3.2 标准

The standard states that Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of the expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.

可以清晰的看到标准中使用了两句话来概括这种问题,这正好对应于SS1,SS2原则。

2.3.3 怎样才能避免写出未定义的表达式出来?

下面给出更为具体的方法:
1)在一个表达式中最多只改变一个实体。
2)如果一个实体在一个表达式被改变,并且出现次数大于一次,请保证所有实体的出现都是为了产生这个“改变”。
例如: i = i+1;
3)如果不能遵守1),那么请保证改变的是不同的实体。
例如:c = *p++;
4)如果1)和2)都不能遵守,那么请使用序列点将表达式分开。
例如 : (c = getchar()) != EOF && c != ‘\n’;

3. 优先级、结合方向做了哪些事,没有做哪些事?

3.1 优先级、结合方向做了哪些事?

C语言中组成程序的基本单位是表达式(expression),表达式是指用操作符(operator)和操作数(operand)连接起来的式子。C标准给出了最基本的操作符,通过这些操作符可以组成简单表达式,同样也可以通过复合产生复杂表达式。当一个表达式中出现多个操作符,多个操作数的时候,操作符合操作数是如何组合起来的呢?优先级和结合方向就是用来解决这个问题的,可以这么说,优先级和结合方向给出了一个表达式的含义,这只是说明了各个操作符和操作数是怎么聚合起来的。

3.2 优先级、结合方向没有做哪些事?

仅仅依靠优先级和结合方向是无法确定一个复合表达式中对各个子表达式的求值顺序。标准中对于这点的规定是:
两个相邻的操作符的执行顺序由它们的优先级决定。如果它们优先级相同,它们的执行顺序由它们的结合性决定。除此之外,编译器可以自由决定任何顺序对表达式进行求值,只要它不违反逗号,&&,||和?:操作符所施加的限制。

4. 解决问题

1)j = i++ + i++ + i++;
这个表达式违反了SS1,不同的编译器产生的结果可能不同。
2)arry[i] = i++;
这个表达式违反了SS2,不同的编译器产生的结果可能不同。
3)x = y = z = 1;
++x || ++y && ++z; PRINT(x, y, z);
&&的优先级比|| 的优先级高,所以:
++x || ++y && ++z 等效于++x || (++y && ++z)
这里就很容易犯错,会认为先执行++y && ++z 在执行++x || ( … ),这种观点是错误的,c中只对 逗号,、逻辑与 &&、
逻辑或 || 和 条件表达式规定了执行顺序,对于逻辑表达式方向是从左向右执行的。本例子中,先执行 ++x = 2,逻辑
或表达式被短路,++y && ++z没有执行,最后x = 2, y = 1, z = 1;

5.后记

通过本课题的学习加深了对于c的理解,理解SS1,SS2原则。在实际应用中应该遵守“一条语句只做一件事的原则”。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容