左移和右移负整数定义行为吗?

发布于 2024-12-19 18:17:48 字数 285 浏览 3 评论 0 原文

我知道,右移负号类型取决于实现,但是如果我执行左移怎么办?例如:

int i = -1;
i << 1;

这个定义明确吗?

我认为标准没有提到带符号类型的负值

如果 E1 具有符号类型和非负值,且 E1 × 2E2 为 可以用结果类型表示,那么这就是结果值; 否则,行为未定义。

它只是澄清,如果结果不能用有符号类型表示,则行为是未定义的。

I know, right shifting a negative signed type depends on the implementation, but what if I perform a left shift? For example:

int i = -1;
i << 1;

Is this well-defined?

I think the standard doesn't say about negative value with signed type

if E1 has a signed type and non-negative value, and E1 × 2E2 is
representable in the result type, then that is the resulting value;
otherwise, the behavior is undefined.

It only clarifies that if the result isn't representable in signed type then the behavior is undefined.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

余厌 2024-12-26 18:17:48

你没有正确地阅读那句话。标准定义它,如果:左操作数有一个有符号类型一个非负值并且结果是可表示的(并且之前在同一段落中将其定义为无符号类型) )。在所有其他情况下(请注意该句子中使用分号),即,如果存在以下任何情况这些条件未经验证,行为未定义。

You're not reading that sentence correctly. The standard defines it if: the left operand has a signed type and a non-negative value and the result is representable (and previously in the same paragraph defines it for unsigned types). In all other cases (notice the use of the semicolon in that sentence), i.e, if any of these conditions isn't verified, the behaviour is undefined.

玉环 2024-12-26 18:17:48

当 C 标准被编纂时,不同的平台在左移负整数时会做不同的事情。对于其中一些,该行为可能会触发特定于实现的陷阱,其行为可能超出程序的控制范围,并且可能包括随机代码执行。尽管如此,为此类平台编写的程序可能会利用此类行为(例如,程序可以指定用户在运行系统之前必须执行某些操作来配置系统的陷阱处理程序,但程序随后可以利用适当配置的陷阱处理程序)。

C 标准的作者不想说,必须修改负数左移会捕获的机器的编译器以防止此类捕获(因为程序可能潜在地依赖它),但是如果左移负数允许触发陷阱,该陷阱可能导致任何任意行为(包括随机代码执行),这意味着允许左移负数执行任何操作。因此未定义的行为。

实际上,直到大约 5 年前,99% 以上为使用补码数学的机器编写的编译器(意味着 1990 年以来制造的 99% 以上的机器)都会始终为 x<x>>y,在某种程度上,对此类行为的代码依赖被认为并不比假设的代码更不可移植char 是 8 位。 C 标准没有强制要求这种行为,但任何想要与现有代码的广泛基础兼容的编译器作者都会遵循它。

  • 如果 y 是有符号类型,则 x << y 和 x>> y 的计算方式与 y 被强制转换为无符号一样。
  • 如果xint类型,则x<相当于(int)((unsigned)x << y)
  • 如果xint类型并且为正数,则x>>y相当于(unsigned)x>>> y 。如果 x 的类型为 int 且为负数,则 x>>y 相当于 ~(~((unsigned)x) >> y)
  • 如果x 的类型为long,则适用类似的规则,但使用unsigned long 而不是unsigned
  • 如果x是N位类型并且y大于N-1,则x>>> yx << y 可以任意产生零,或者可以表现为右侧操作数是y % N;它们可能需要与 y 成正比的额外时间 [请注意,在 32 位机器上,如果 y 为负数,则可能是一个时间,尽管我只知道一台机器实际上会运行超过 256 个额外步骤]。编译器的选择不一定一致,但总是会返回指示值之一,而没有其他副作用。

不幸的是,由于我不太明白的原因,编译器编写者决定,编译器不应允许程序员指示编译器应使用哪些假设来删除死代码,而应假设不可能执行其行为未强制执行的任何转换。 C 标准。因此,给定如下代码:

uint32_t shiftleft(uint32_t v, uint8_t n)
{
  if (n >= 32)
    v=0;
  return v<<n;
}

编译器可能会确定,因为当 n 为 32 或更大时,代码将参与未定义行为,所以编译器可能会假设 if 永远不会返回 true,因此可能省略代码。因此,除非或直到有人提出恢复经典行为并允许程序员指定哪些假设值得删除死代码的 C 标准,否则不能为任何可能输入超现代编译器的代码推荐此类构造。

When the C standards were codified, different platforms would do different things when left-shifting negative integers. On some of them, the behavior might trigger implementation-specific traps whose behavior could be outside a program's control, and which could include random code execution. Nonetheless, it was possible that programs written for such platforms might make use of such behavior (a program could e.g. specify that a user would have to do something to configure a system's trap handlers before running it, but the program could then exploit the behavior of the suitably-configured trap handlers).

The authors of the C standard did not want to say that compilers for machines where left-shifting of negative numbers would trap must be modified to prevent such trapping (since programs might potentially be relying upon it), but if left-shifting a negative number is allowed to trigger a trap which could cause any arbitrary behavior (including random code execution) that means that left-shifting a negative number is allowed to do anything whatsoever. Hence Undefined Behavior.

In practice, until about 5 years ago, 99+% of compilers written for a machine that used two's-complement math (meaning 99+% of machines made since 1990) would consistently yield the following behaviors for x<<y and x>>y, to the extent that code reliance upon such behavior was considered no more non-portable than code which assumed char was 8 bits. The C standard didn't mandate such behavior, but any compiler author wanting to be compatible with a wide base of existing code would follow it.

  • if y is a signed type, x << y and x >> y are evaluated as though y was cast to unsigned.
  • if x is type int, x<<y is equivalent to (int)((unsigned)x << y).
  • if x is type int and positive, x>>y equivalent to (unsigned)x >> y. If x is of type int and negative, x>>y is equivalent to ~(~((unsigned)x) >> y).
  • If x is of type long, similar rules apply, but with unsigned long rather than unsigned.
  • if x is an N-bit type and y is greater than N-1, then x >> y and x << y may arbitrarily yield zero, or may act as though the right-hand operand was y % N; they may require extra time proportional to y [note that on a 32-bit machine, if y is negative, that could potentially be a long time, though I only know of one machine which would in practice run more than 256 extra steps]. Compilers were not necessarily consistent in their choice, but would always return one of the indicated values with no other side-effects.

Unfortunately for reasons I can't quite fathom, compiler writers have decided that rather than allowing programmers to indicate what assumptions compilers should use for dead-code removal, compilers should assume that it is impossible to execute any shift whose behavior isn't mandated by the C standard. Thus, given code like the following:

uint32_t shiftleft(uint32_t v, uint8_t n)
{
  if (n >= 32)
    v=0;
  return v<<n;
}

a compiler may determine that because the code would engage in Undefined Behavior when n is 32 or larger, the compiler may assume that the if will never return true, and may thus omit the code. Consequently, unless or until someone comes up with a standard for C which restores the classic behaviors and allows programmers to designate what assumptions merit dead code removal, such constructs cannot be recommended for any code that might be fed to a hyper-modern compiler.

情栀口红 2024-12-26 18:17:48

否则,行为未定义。

这包括

如果 E1 具有符号类型和非负值

otherwise, the behavior is undefined.

This includes the

if E1 has a signed type and non-negative value

知足的幸福 2024-12-26 18:17:48

在 C++20 中,定义了左移负整数(以唯一合理的方式):

E1的值<< E2 是与 E1 × 2E2 模 2N 一致的唯一值,其中 N 是结果类型的宽度。

这句话适用于有符号和无符号整数。虽然这可能不是很明显,但对于有符号整数来说,这意味着只需移动位,没有废话。

在 C++20 中,定义了右移负整数(以唯一合理的方式):

E1的值>> E2 为 E1/2E2,向下舍入。

这句话适用于有符号和无符号整数。对于有符号整数,这意味着移位必须是算术移位

负(或大)移位量仍然是 UB:

如果右操作数为负数,或者大于或等于提升的左操作数的宽度,则行为未定义。

注意:我添加这个答案不是因为它直接按原样回答问题,而是因为人们链接到这个问题来证明移位负整数是未定义的或实现已定义。以前是,但现在不是了。

In C++20, left-shifting negative integers is defined (in the only sensible way):

The value of E1 << E2 is the unique value congruent to E1 × 2E2 modulo 2N , where N is the width of the type of the result.

This sentence applies to both signed and unsigned integers. Although it may not be immediately obvious, for signed integers this means just shift the bits, no nonsense.

In C++20, right-shifting negative integers is defined (in the only sensible way):

The value of E1 >> E2 is E1/2E2, rounded down.

This sentence applies to both signed and unsigned integers. For signed integers, it means that the shift must be an arithmetic shift.

Negative (or large) shift amount is still UB:

The behavior is undefined if the right operand is negative, or greater than or equal to the width of the promoted left operand.

note: I've added this answer not because it directly answers the question as-is, but because people are linking to this question to prove that shifting negative integers is undefined or implementation defined. It used to be, but now it's not.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文