通过位移/屏蔽来体验奇怪的行为

时间:2013-09-22 00:29:17

标签: c bit-manipulation undefined-behavior bit-shift

Backstory:我正在开发一个玩具编译器,它采用一些简化的类似汇编的文本并将其转换为32位指令。

转换为32位指令工作正常,但是当我尝试输出结果时,我遇到了一些问题。具体来说,我写的用于提取各个部分的宏似乎是以我无法解释的方式修改价值。

以下循环是我遇到问题的地方(位操作存储在宏中,但我将它们扩展出来以便进行更简单的调试):

while(pc < num_insts)
{
   printf("full: %x | op: %x | rs: %x | rt: %x | rd: %x | imm: %x\n",
      inst_mem[pc],
      ((inst_mem[pc] >> 26) & 0x0000003F),
      ((inst_mem[pc] >> 21) & 0x0000003F),
      ((inst_mem[pc] >> 16) & 0x0000003F),
      ((inst_mem[pc] >> 11) & 0x0000F800),
      (inst_mem[pc++] & 0x0000FFFF));
}

其中列出以下内容:

full: 20450008 | op: 8 | rs: 2 | rt: 5 | rd: 0 | imm: 10
full: 0 | op: 0 | rs: 0 | rt: 0 | rd: 0 | imm: 8

这些行的正确值为:

full: 20450008 | op: 8 | rs: 1 | rt: 4 | rd: 0 | imm: 16
full: 20240010 | op: 8 | rs: 2 | rt: 5 | rd: 0 | imm: 8

如果我用更简单的

替换它
while(pc < num_insts)
{
   printf("full: %x", inst_mem[pc++]);
}

然后按照我的预期输出每个完整地址。这对我来说意味着解析全部正常工作,并且我的宏在解析之后将值从地址中拉出来并且用他们不应该的东西进行顶升。我只是不确定是什么会是什么。

由于inst_mem[]包含int32_t值且所有班次都小于32,因此不应该是无证移位行为的问题。

如果有人能够在正确的方向上轻推,我将不止感激。我的想法已经不多了。

2 个答案:

答案 0 :(得分:3)

函数参数的评估顺序不一定是“第一个参数”。因此,当其他函数参数使用pc++时,如pc之类的增量是非常危险的。之后将pc++更改为pc并增加pc

while(pc < num_insts)
{
   printf("full: %x | op: %x | rs: %x | rt: %x | rd: %x | imm: %x\n",
      inst_mem[pc],
      ((inst_mem[pc] >> 26) & 0x0000003F),
      ((inst_mem[pc] >> 21) & 0x0000003F),
      ((inst_mem[pc] >> 16) & 0x0000003F),
      ((inst_mem[pc] >> 11) & 0x0000F800),
      (inst_mem[pc] & 0x0000FFFF));
   pc++;
}

答案 1 :(得分:1)

该计划的行为包括unspecifiedundefined。如果我们查看C99 draft standard部分6.5.2.2 函数调用 10 ,那么函数参数的评估顺序是未指定的(强调雷):

  

函数指示符的评估顺序,实际参数和   实际参数中的子表达式未指定,但有一个序列点   在实际通话之前。

因此我们无法确定何时将对其他参数执行子表达式inst_mem[pc++] & 0x0000FFFF,因此我们不知道何时递增pc

这是未定义的行为,因为如果我们查看部分6.5 表达式 2 说(强调我的< / em>的):

  

在上一个和下一个序列点之间,对象应具有其存储值   通过表达式的评估最多修改一次.72)此外,先前的值   应只读以确定要存储的值 .73)

并在脚注 73 中提供了以下未定义行为的示例

i = ++i + 1;
a[i++] = i;

因此,如果对象被修改,则在sequence point内,只能读取先前的值以确定要存储的值。在您的代码中pc正在使用pc++表达式进行修改,并且正在多次读取以确定数组索引,从而调用未定义的行为。

此修复是直截了当的,即将pc++移出printf来电。