放弃一个值会导致读取它吗?

7
请参考以下示例:
{
  int x;
  (void)x; // silence the "unused" warning
  ...
}

如果由于未初始化读取x会导致未定义的行为吗?如果是,那么这是否意味着在以下代码中,编译器必须发出内存读取指令(以读取指针所指向的内容)?

volatile char* p=getP();
(void)*p;

我对C和C ++的相关规则都感兴趣,以防它们不同。


2
在 C 语言中,我唯一需要定义一个未使用的变量的时候是当这个变量是函数的参数,并且该函数必须满足外部强制接口(通常会通过指向函数的指针进行调用,并且指向函数的指针集合的接口是固定的)。在这种情况下,参数由调用机制初始化,因此不存在 UB。我想不出其他任何理由为什么要定义 int x; 如果它没有被使用。(在 C++ 中,如果要表示参数未使用,可以为参数定义一个无名称的函数 - 因此您不需要进行转换。) - Jonathan Leffler
1
你需要澄清x在哪个作用域内被声明,否则问题无法回答。我假设是局部作用域? - Lundin
@Lundin 是的,在块作用域内。也就是说,x 是自动的。 - Ruslan
1
@JonathanLeffler 有时候在嵌入式系统的非易失性存储器中有volatile const变量,你想要为它们在某个内存位置保留空间,以备将来使用。即使程序尚未使用这些变量。在某处将它们转换为void类型通常可以触发变量被链接而不是仅仅被优化掉。 - Lundin
@Lundin:但在这种情况下,我认为定义和空指针使用不会出现在代码的连续行上。另外,我不编写嵌入式系统代码,所以修饰语“我需要的唯一时间”仍然准确。然而,您可能正确,有些人需要拉一些有趣的恶作剧,但我怀疑在声明周围会有措辞 - 诸如注释和/或编译器特定提示等 - 以明确说明什么和为什么。 - Jonathan Leffler
在嵌入式系统中,有时会使用(void)*(volatile int *)address(或其等效物)来获取并丢弃寄存器,这本身可能会产生一些影响,例如清除标志(状态/中断 - 获取中断状态/挂起寄存器可以防止它立即重新发生,您可以针对各种错误进行中断,以此方式忽略特定类型并重新设置状态)。 (我个人更喜欢使用返回寄存器值的方法-调用它但忽略结果。) - firda
2个回答

1
在C++中,访问与左值到右值转换相关联。这种转换是将“标识”转换为其值的过程。关于被丢弃的值表达式,C++标准如下所述:

[expr] (强调是我的)

12 在某些情况下,表达式只出现在其副作用中。这种表达式称为丢弃值表达式。数组到指针和函数到指针的标准转换不适用。当且仅当表达式是易失性限定类型的glvalue之一,并且是以下之一时,应用lvalue-to-rvalue转换:

  • (expression),其中expression是这些表达式之一,
  • id-expression,
  • 下标操作,
  • 类成员访问,
  • 间接寻址,
  • 指向成员的操作,
  • 条件表达式,第二个和第三个操作数都是这些表达式之一,或者
  • 逗号表达式,右操作数是这些表达式之一。

[注:使用重载运算符会导致函数调用;上述内容仅涵盖具有内置含义的运算符。 — 末注 ] 如果此可选转换后表达式是prvalue,则应用临时材料化转换。 [注:如果表达式是类类型的lvalue,则必须具有易失性复制构造函数才能初始化是lvalue-to-rvalue转换的结果对象的临时对象。 — 末注 ] 评估glvalue表达式并丢弃其值。

在非易失性情况下,不存在左右值转换。因此我们可以自信地说变量未被访问。
然而,在易失性情况下,您需要在强制转换表达式内进行间接操作,这将经历左右值转换。因此,易失性变量已被读取,并且从读取未初始化对象中获得未定义行为。
为避免易失性glvalues的未定义行为,可以将相关变量注释为[[maybe_unused]]。这是被认可的方式,并且我更喜欢使用它,而不是在非易失性情况下进行强制转换。

但 C 语言呢?它不能有完全相同的规则,因为它没有出现在 C++11 中的扩展_value概念(尽管措辞可能将它们简单地“平铺”到旧概念中)。此外,它也没有 [[maybe_unused]] 或任何其他属性。 - Ruslan
1
@Ruslan - 看,这就是双重标记的问题。它既变得太宽泛,也变得太狭窄,无法提供替代方案。我给出了一个C++的答案,因为它对我来说更容易获得。我正在寻找C的规范参考,但也许有人会先提供它。 - StoryTeller - Unslander Monica
这个引用还说:“* glvalue表达式被评估并且其值被丢弃。*”听起来有矛盾之处。那么 [basic.life] / 7.1呢? - rustyx
@rustyx - “glvalue” 被求值,以产生一个标识。而这总是被丢弃的。"prvalue" 的产生,即访问,是有条件的。我从 OP 的隐含假设中推断出存在一个对象。我们不要把 [basic.life]/7 拖进来。 - StoryTeller - Unslander Monica

1

C标准中相关部分:

6.3.2.2 void

一个 void表达式(即类型为void的表达式)的(不存在的)值不得以任何方式使用,并且不能对这种表达式进行隐式或显式转换(除了转换为void)。如果将任何其他类型的表达式作为void表达式来求值,则其值或指示符将被丢弃。(void表达式会由于其副作用而被求值。)

其中,副作用由5.1.2.3/2定义:

访问易变对象、修改对象、修改文件或调用执行上述操作的函数都是副作用,即改变执行环境状态的操作。

读取非易变变量不是副作用。

也就是说,如果访问x是副作用,则必须评估(执行)代码。只有当xvolatile时才会出现这种情况。因此,(void)x;不会触发未定义行为。

否则,在作用域内从未使用局部变量x的地址,使用它将导致未定义的行为。
在*p的情况下,您通过*运算符对易失限定变量进行了明确的左值访问,因此编译器必须读取该变量。无论是否转换为(void)。
下面的示例也将被评估,但会引发未定义的行为(除非获取指针p本身的地址):
char* volatile p;
(void)p;

“所以 (void)x; 不会触发未定义行为” 似乎并不符合引用的含义。即使它的值被丢弃,由于“_as-if_”规则的影响,仍然可能读取 x(或者可能不读取)。我错了吗? - Ruslan
@Ruslan 不,“它的值或标识符被丢弃”。而且由于没有副作用,也就没有什么被评估的东西。副作用在正式定义中是“访问易失对象、修改对象、修改文件或调用执行任何这些操作的函数”。已在答案中添加了澄清。 - Lundin
x(void) x 不是一个 void 表达式。它是转换为 void 的操作数。它在 void 表达式内部,不是 void 表达式。因此,6.3.2.2 不适用于它。 - Eric Postpischil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接