【问题标题】:What is a side effect in C?C语言的副作用是什么?
【发布时间】:2021-01-13 05:39:04
【问题描述】:

在 C99 第 5.1.2.3 条第 2 段中,

访问 volatile 对象、修改对象、修改文件或调用函数 执行这些操作中的任何一个都是副作用,它们是状态的变化 执行环境。

C 标准定义了 副作用 就像上面一样。但似乎 C99 没有解释访问 volatile 对象、修改对象、修改文件的确切含义(在第 3 节中定义了访问、修改、对象的定义是什么。但是如何访问 volatile 呢?修改什么对象? 和修改什么文件?)。
当我用词副作用搜索时,C99 中有一些示例。但是我不能确定每个示例是否被分类为访问易失性对象、修改对象和修改文件。
我阅读了What is side effect in c? 和Are side effects a good thing?,但仍然感到困惑。
我的问题是C 标准是否明确描述了副作用 的含义?它们是什么意思?

【问题讨论】:

  • 他们打算说 C 不是一种函数式语言。在(纯)函数式语言中,没有副作用。
  • @wildplasser 那么modifying an object中的对象不代表C源代码中的任何标识符吗?
  • @opol 一个对象不需要在 C 中具有 am 标识符,只需一个指定它的左值表达式。

标签: c side-effects


【解决方案1】:

访问 volatile 对象意味着 通过 volatile 限定的左值读取 volatile-qualified 对象/对象的值 - 标准规定这些需要评估“严格 根据抽象机的规则”。

修改对象意味着修改任何对象 - 修改任何东西都被视为副作用。示例:赋值运算符具有修改它分配值的变量的副作用!在下面的程序中。赋值运算符用于它的副作用!

修改文件意味着写入文件、创建文件、删除文件等等 - 任何构成更改的事情。

这些类别的副作用示例:

void increment(int *p) {
    (*p) ++; // side effect - assign a new value to the 
             // object pointed to by p
}


int a = 5;
volatile int b = 6;
if (b == 6) { // side-effect of accessing a volatile variable
    a += b;   // calculate a + b, and as a side effect assign a new 
              // value to a
}

increment(&a);     // side effect - call a function that does
                   // one of the aforementioned operations
printf("%d\n", a); // side effect - change the state of an output stream

FILE *fp = fopen("foo", "w"); // side effect - create or truncate
fputc('!', fp); // side effect - modify file
fclose(fp);.    // side effect - close the file, flush 
remove("bar");. // side effect - remove file

有一种(更小的)编程语言类别,称为纯函数式语言,例如 Haskell,其计算是无副作用的。 C 不是这样的语言之一。

【讨论】:

  • 不是我的投票,但分配并不总是仅用于其副作用。 for (i = j = k = 0; …) 使用 j = k = 0 和 k = 0 作为它们的值)。
  • 此外,这些示例都不一定显示文件的副作用。如果流是完全缓冲的,printf 可能只更改由内存中的数据结构构成的流。更改不一定会立即到达文件。
  • @Eric Postpischil 你想从for (i = j = k = 0; ...) 说什么使用j = k = 0 和k = 0? .在这种情况下,= 用于分配给对象,这会导致*副作用*(我的想法)。
  • 我删除了“仅”
  • @opol:它们用于副作用,但并非仅用于副作用。每当赋值出现在链中或赋值的值在表达式中以其他方式使用时(例如,int c; while ((c = getchar()) != EOF)…),它们也用于它们的值。
【解决方案2】:

我的问题是,C 标准是否明确描述了副作用的含义?

您引用的 C 标准中的句子(C 1999 5.1.2.3 2,C 2018 中相同)明确描述了副作用的含义。这些将在下面进一步解释。

修改对象

修改对象被理解为包括更新表示对象的存储字节的内容。我相信它们的完整列表是:

  • 简单赋值(=)和复合赋值(*=,/=%=,+=,-=,<<=,>>=,@987654329,@,@80 |=)。
  • 递增和递减运算符(++ 和 --),包括前缀和后缀。
  • 对其定义中包含的对象进行初始化。
  • 指定用于更改对象的库例程,例如memcpy。

访问易失性对象

“访问”在 C 2018 3.1 中定义为“⟨执行时动作⟩读取或修改对象的值”。如果x 是volatile int,则在表达式中使用x 的值会访问它(在计算表达式时),因为它读取x 的值。您可以更具体地遵循这一点,因为 6.3.2.1 2 告诉我们在表达式中使用 x 会导致采用 x 的值:

除非它是sizeof 运算符、一元& 运算符、++ 运算符、-- 运算符或. 运算符或赋值运算符的左操作数,没有数组类型的左值被转换为存储在指定对象中的值(并且不再是左值);这称为左值转换。

因此,表达式中的x 本身只是对象x 的指定,它被转换为存储在x 中的值,这意味着存储的值是从内存中读取的。那是x的访问权限。

修改 volatile 对象与修改任何对象相同,如上所述。

修改文件

通过第 7.21 节中定义的例程(“输入/输出<stdio.h>”)修改文件。

【讨论】:

  • 我会补充说,如果该函数修改了除其自身参数(但当然不是指针参数引用的对象)或其本地对象之外的任何内容,则该函数会产生副作用。
  • 我不会把初始化算作副作用
猜你喜欢
  • 2023-03-24
  • 1970-01-01
  • 2012-08-16
  • 1970-01-01
  • 2011-06-17
  • 2014-09-09
  • 2011-05-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多