C语言的副作用测试实例及序列点

测试例子1:

#include

int main(void) {
  int a = 5;
  printf("%d, %d, %d\n", (a++) , (a++) , (a++));
  printf("a = %d\n", a);
  return 0;
}

编译执行:

$ gcc foo.c
$ ./a.out
7, 6, 5
a = 8

测试例子2:

#include

int main(void) {
  int a = 5;
  printf("s = %d\n", (a++) + (a++) + (a++));
  printf("a = %d\n", a);
  return 0;
}

编译执行:

$ gcc-4.7 foo.c
$ ./a.out
s = 15
a = 8

$ gcc-4.8 foo.c
$ ./a.out
s = 18
a = 8

原因在于C语言标准中关于side effect和sequence point的描述和规范。这里有篇博文,介绍的非常到位(多谢kito-cheng提供)http://blog.tinlans.org/2010/08/06/sequence-point/

C语言标准中的原文如下:

The result of the postfix ++ operator is the value of the operand. After the result is
obtained, the value of the operand is incremented. (That is, the value 1 of the appropriate
type is added to it.) See the discussions of additive operators and compound assignment
for information on constraints, types, and conversions and the effects of operations on
pointers. The side effect of updating the stored value of the operand shall occur between
the previous and the next sequence point.

Evaluation of an expression may produce side effects. At
certain specified points in the execution sequence called sequence points, all side effects
of previous evaluations shall be complete and no side effects of subsequent evaluations
shall have taken place.

C语言中的序列点和副作用

    C 语言中,术语副作用(side effect)是指对数据对象或者文件的修改。例如,以下语句        var = 99;
    的副作用是把 var 的值修改成 99。对表达式求值也可能产生副作用,例如:
            se = 100
    对这个表达式求值所产生的副作用就是 se 的值被修改成 100。
       序列点(sequence point)是指程序运行中的一个特殊的时间点,在该点之前的所有副作用已经结束,并且后续的副作用还没发生。
        C 语句结束标志——分号(;)是序列点。也就是说,C 语句中由赋值、自增或者自减等引起的副作用在分号之前必须结束。我们以后会说到一些包含序列点的运算符。任何完整表达式(full expression)运算结束的那个时间点也是序列点。所谓完整表达式,就是说这个表达式不是子表达式。而所谓的子表达式,则是指表达式中的表达式。例如:
            f = ++e % 3
    这整个表达式就是一个完整表达式。这个表达式中的 ++e、3 和 ++e % 3 都是它的子表达式。
        有了序列点的概念,我们下面来分析一下一个很常见的错误:
            int x = 1, y;
            y = x++ + x++;
    这里 y = x++ + x++ 是完整表达式,而 x++ 是它的子表达式。这个完整表达式运算结束的那一点是一个序列点,int x = 1, y; 中的 ; 也是一个序列点。也就是说,x++ + x++ 位于两个序列点之间。标准规定,在两个序列点之间,一个对象所保存的值最多只能被修改一次。但是我们清楚可以看到,上面这个例子中,x 的值在两个序列点之间被修改了两次。这显然是错误的!这段代码在不同的编译器上编译可能会导致 y 的值有所不同。比较常见的结果是 y 的值最后被修改为 2 或者 3。在此,我不打算就这个问题作更深入的分析,各位只要记住这是错误的,别这么用就可以了。有兴趣的话,可以看看以下列出的相关资料。
    C 语言标准对副作用和序列点的定义如下:
        Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression may produce side effects. At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.
    翻译如下:
        访问易变对象,修改对象或文件,或者调用包含这些操作的函数都是副作用,它们都会改变执行环境的状态。计算表达式也会引起副作用。执行序列中某些特定的点被称为序列点。在序列点上,该点之前所有运算的副作用都应该结束,并且后继运算的副作用还没发生。

    ----------------------------------------------------------------------------------------------
    让我们来看看下面的代码:
    int i=7; printf(”%d\n”, i++ * i++);
    你认为会返回什么?56?no。正确答案是返回 49?很多人会问为什么?难道不该打印出56吗?在ccfaq中有非常详尽的解释,根本原因在于c中的序列点。
    请注意,尽管后缀自加和后缀自减操作符 ++ 和 — 在输出其旧值之后才会执行运算,但这里的“之后”常常被误解。没有任何保证确保自增或自减会在输出变量原值之后和对表达式的其它部分进行计算之前立即进行。也不能保证变量的更新会在表达式 “完成” (按照 ANSI C 的术语, 在下一个”序列点”之前) 之前的某个时刻进行。本例中, 编译器选择使用变量的旧值相乘以后再对二者进行自增运算。只有到达一个序列点之后,自增运算才能保证真正被执行。
    包含多个不确定的副作用的代码的行为总是被认为未定义。(简单而言, “多个不确定副作用”是指在同一个表达式中使用导致同一对象修改两次或修改以后又被引用的自增,自减和赋值操作符的任何组合。这是一个粗略的定义。) 甚至都不要试图探究这些东西在你的编译器中是如何实现的 (这与许多 C 教科书上的弱智练习正好相反);正如 K&R 明智地指出,”如果你不知道它们在不同的机器上如何实现, 这样的无知可能恰恰会有助于保护你”。
    那么,所谓的序列点是什么意思呢?
    序列点是一个时间点(在整个表达式全部计算完毕之后或在 ||、 &&、 ? : 或逗号 运算符处, 或在函数调用之前), 此刻尘埃落定,所有的副作用都已确保结束。 ANSI/ISO C 标准这样描述:
    在上一个和下一个序列点之间,一个对象所保存的值至多只能被表达式的计算修改一次。而且前一个值只能用于决定将要保存的值。
    第二句话比较费解。它说在一个表达式中如果某个对象需要写入, 则在同一表达式中对该对象的访问应该只局限于直接用于计算将要写入的值。这条规则有效地限制了只有能确保在修改之前才访问变量的表达式为合法。
    例如 i = i+1 合法,而 a[i] = i++ 则非法。为什么这样的代码:a[i] = i++; 不能工作?子表达式 i++ 有一个副作用 — 它会改变 i 的值 — 由于 i 在同一表达式的其它地方被引用,这会导致无定义的结果,无从判断该引用(左边的 a[i] 中)是旧值还是新值。那么,对于 a[i] = i++; 我们不知道 a[] 的哪一个分量会被改写,但 i 的确会增加 1,对吗?
    不一定!如果一个表达式和程序变得未定义,则它的所有方面都会变成未定义。
    为什么&& 和 || 运算符可以产生序列点呢?这些运算符在此处有一个特殊的例外:如果左边的子表达式决定最终结果 (即,真对于 || 和假对于 && ) ,则右边的子表达式不会计算。因此,从左至右的计算可以确保,对逗号表达式也是如此。而且,所有这些运算符 (包括 ? : ) 都会引入一个额外的内部序列点。

时间: 2025-01-19 09:57:48

C语言的副作用测试实例及序列点的相关文章

不定参数在C语言中的应用实例

不定参数在C语言中的应用实例:不定参数当年做为C/C++语言一个特长被很多人推崇,但是实际上这种技术并没有应用很多.除了格式化输出之外,我实在没看到多少应用.主要原因是这种技术比较麻烦,副作用也比较多,而一般情况下重载函数也足以替换它.尽管如此,既然大家对它比较感兴趣,我就简单总结一下它的使用和需要注意的常见问题. 刚学C语言的时候,一般人都会首先接触printf函数.通过这个函数,你可以打印不定个数的变量到屏幕,如: printf("%d", 3): printf("%d,

mootools框架【二】-Core篇:主要方法测试实例

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD

压力测试实例

利用现代的设计技术和正式的技术复审可以减少代码中存在的初始错误,但是错误总是存在的,如果开发者找不到错误,那么,客户就会找到它们.越来越多的软件组织认识到软件测试是软件质量保证的重要元素之一,很多软件开发组织将30%-40%甚至更多的项目资源用在测试上,软件测试技术和软件测试策略受到了高度的重视和广泛的应用. 本文不想就软件测试技术和软件测试策略作深入的理论分析,而是列举一个在软件系统测试阶段进行的压力测试实例,希望能通过这个实例与从事软件测试相关工作的朋友进行交流. 首先介绍一下实例中软件的项

原生javascript兼容性测试实例

原生javascript兼容性:currentStyle.scrollTop.event以及绑定事件IE的绑定事件为attachEvent/detachEvent等等兼容性测试实例,感兴趣的朋友可以参考下哈   1.获取样式表里面的width,border color 之类的css(不是行间) 主要是IE6-7支持currentStyle,标准浏览器支持getComputedStyle; 实例:封装函数 复制代码 代码如下: function getStyle(obj,name){ if(obj

Swift与C语言指针结合使用实例

  这篇文章主要介绍了Swift与C语言指针结合使用实例,本文讲解了用以输入/输出的参数指针.作为数组使用的参数指针.用作字符串参数的指针.指针参数转换的安全性等内容,需要的朋友可以参考下 Objective-C和C的API常常会需要用到指针.Swift中的数据类型都原生支持基于指针的Cocoa API,不仅如此,Swift会自动处理部分最常用的将指针作为参数传递的情况.这篇文章中,我们将着眼于在Swift中让C语言指针与变量.数组和字符串共同工作. ####用以输入/输出的参数指针 C和Obj

关于phprpc测试实例

  关于phprpc测试实例 一. 服务接口定义 package com.yanek.study.phprpc; public interface Hello {            String say(String name);    } 二 . 服务接口实现 package com.yanek.study.phprpc; /**    * 服务接口实现  *    *  */   public class MyHello implements Hello {           publ

linux下基于C语言的信号编程实例_C 语言

本文实例讲述了linux下基于C语言的信号编程方法.分享给大家供大家参考.具体如下: #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> void sig_handler(int sig_no, siginfo_t *info, void *ctext){ printf("receive si

一款C#多线程测试实例

一款c#多线程测试实例 简单实例 namespace xxx {     public class tclass     {          static tclass()          {              thread r = new thread(new threadstart(inusrgame));              r.start();          }     } } 详细多线程 using system; using system.threading;

C++中CSimpleList的实现与测试实例_C 语言

本文实例讲述了C++简单列表类的实现方法.分享给大家供大家参考.具体方法如下: _AFXTLS.CPP文件如下: //#include "StdAfx.h #include <stddef.h> #include <stdio.h> #include "_AFXTLS_.H" struct MyThreadData{ MyThreadData* pNext; int nShortData; }; void CSimpleList::AddHead(vo