【问题标题】:Understanding of integral arithmetics in C理解 C 中的积分算术
【发布时间】:2019-07-30 13:24:37
【问题描述】:

编辑:将 void * 修改为 uint8_t * 值。问题依然存在。

编辑:问题是一个简单的变量溢出,与整数提升无关。

我解决了那段简化代码中的一个错误。类型与源代码中的相同。

unsigned int entrySize;    // entrySize is 288
int startIndex, endIndex;  // both are 24536838
uint8_t *pStartAddr;          // valid initialized pointer (0x34f1e40)

/*Mystery begins...*/
uint8_t *curr_addr = pStartAddr + entrySize * startIndex;
while (curr_addr <= startAddr + entrySize * endIndex)
{
    externFunc(curr_addr);
    curr_addr+=entrySize;
}

快速浏览此代码似乎很明显,不包括奇怪的类型选择。

然而,在我们的一次崩溃中,curr_addr 似乎获得了一个无效指针。 我的预感是 entrySize * startIndex 存在问题,因为它们的乘法集在第 32 位,并且将 startIndexendIndex 作为有符号类型可能会使编译器混淆要使用的所需值。

在更改了它们的类型后,问题解决了。 但我无法弄清楚究竟是什么出了问题。

我正在使用 64 位机器、x86_64 CPU、gcc (GCC) 4.8.5 20150623 和 linux red hat 发行版(版本 4.8.5-28)

我假设当上面的计算设置在entrySize * startIndex 的第 32 位时问题开始发生。但是,当我使用第一个打开第 32 位的 startIndex 值时,它仍然有效。它也显示了

我的问题是:

  • int*unsigned int的值结果被认为是有符号或 未签名?结果类型的等级是多少?乘法可以 可能溢出到 8 字节类型,我假设编译器阻止 失去精度,对吧?
  • startAddrvoid*。然后添加 到第一个问题中计算的任何值和类型。是void* 考虑signedunsigned?我的预感当然是unsigned value,但我无法支持。
  • 什么整数提升发生在 startAddr + result>>。
  • 我们的while 语句是否可以永远停止(实际上)?如果不等式的右边是有符号数(宽度至少为 8 字节),那么左边(curr_addr)是否也会被提升为有符号数,导致死循环?
  • 欢迎您逐步解释:)

我阅读了这些链接中包含的内容,但仍然一无所知:

  1. https://www.oreilly.com/library/view/c-in-a/0596006977/ch04.html
  2. Integer conversion rank of signed and unsigned int

【问题讨论】:

  • @IgorGalczak 所有的值都已经定义好了,不用担心 :) startAddr 实际上是来自全局符号的地址(&variableName 来自数据段)
  • 如果这段代码是“定义明确的”,你就不会问这么严重的问题,关于“定义明确”的代码到底发生了什么。该代码混合了有符号和无符号整数运算,在溢出时容易出现未定义的行为,并将其与使用void * 指针的计算相结合。
  • @AndrewHenle 我提到的值是流中使用的变量。 startAddr 中设置的指针有效。你是对的,之后发生的事情远没有得到很好的定义。我仍在为我自己寻找一个好的解释,也为将来像我这样的人着想。
  • 你不能对空指针使用任何形式的算术。

标签: c types integer-overflow


【解决方案1】:
  1. void* 上的指针运算行为未定义。

  2. 指针算法仅在数组中有效。请注意,您可以设置一个指向数组最后一个元素的指针,但不要尝试取消引用它。这条规则也适用于可以被视为单元素数组的对象。

(1) 在您的代码中肯定没有被遵守(您的编译器没有警告您 - 如果没有,请将其装箱),(2)可能不会。有点滑稽的是(就我而言),你的具体问题并不相关。

【讨论】:

  • "指针算术在 void* 上的行为是未定义的" 甚至不是这样,它违反了约束,这意味着它不能干净地编译。分别为 C17 6.5.5 和 6.5.6。
  • 它确实可以编译,也许某些编译允许它,我看看它
【解决方案2】:

乘法可能会溢出到 8 字节类型,我假设编译器可以防止丢失精度,对吧?

这是一个非常错误的假设。每the 3.4.3 description of undefined behavior from the C standard(我的粗体字):

3.4.3

1 未定义的行为行为,在使用不可移植或错误的 程序构造或错误数据,为此国际 标准没有要求

2 注意可能的未定义行为范围从忽略情况 完全具有不可预测的结果,在翻译过程中表现得很好 或以文件化方式执行程序的特征 环境(无论是否发布诊断消息),以 终止翻译或执行(发出 诊断信息)。

3 示例 未定义行为的一个示例是整数行为 溢出。

整数溢出就是 C 标准本身中使用的未定义行为的一个例子。

288 * 24536838 等于7066609344,这远远超出了32 位int 的容量,无论是signed 还是unsigned,从而调用未定义的行为。

所以不,编译器不会“防止丢失精度”。事实上恰恰相反。

【讨论】:

  • 哇,关于7066609344,你完全正确!我使用了 Windows 10 计算器,但我忘记了这些位是从 0 开始的!我提到的 32 位实际上是第 33 位。很好,我需要消化一下这些信息
  • 哦,好吧,在意识到我发现这只是一个溢出问题之后。索引 14913080 和 14913081。14913081 * 288 导致值高于 32 位,从而导致溢出错误的指针方向。你的答案是壁橱,所以非常感谢你
猜你喜欢
  • 2016-04-01
  • 2014-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-19
  • 2012-06-24
相关资源
最近更新 更多