c 标准和位移

发布于 2024-12-12 21:56:08 字数 1906 浏览 3 评论 0原文

这个问题首先受到此代码的(意外)结果的启发:

uint16_t   t16 = 0;
uint8_t     t8 = 0x80;
uint8_t t8_res;

t16    = (t8 << 1);
t8_res = (t8 << 1);

printf("t16: %x\n", t16);    // Expect 0, get 0x100
printf(" t8: %x\n", t8_res); // Expect 0, get 0

但事实证明这是有道理的:

6.5.7 按位移位运算符

约束

2 每个操作数都应具有整数类型

因此,最初混淆的行相当于:

t16 = (uint16_t) (((int) t8) << 1);

恕我直言,有点不直观,但至少定义良好。

好的,很好,但是我们这样做:

{
uint64_t t64 = 1;
t64 <<= 31;
printf("t64: %lx\n", t64); // Expect 0x80000000, get 0x80000000
t64 <<= 31;
printf("t64: %lx\n", t64); // Expect 0x0, get 0x4000000000000000
}

// 编辑:遵循与上面相同的文字参数,以下内容应该是等效的:

t64 = (uint64_t) (((int) t64) << 31);

// 因此我的困惑/期望 [end_edit]

现在,我们得到了直观的结果,但不是什么来自我对标准的(字面)阅读。这种“进一步自动类型提升”何时/如何发生?或者是否存在其他地方的限制,即类型永远不能降级(这有意义吗?),在这种情况下,提升规则如何适用:

uint32_t << uint64_t

因为标准确实规定两个参数都提升为 int;是否应该将两个参数提升为相同类型?

// edit:

更具体地说,以下结果应该是什么:

uint32_t t32 = 1;
uint64_t t64_one = 1;
uint64_t t64_res;

t64_res = t32 << t64_one;

// end edit

当我们认识到规范并不要求具体提升为 int 而是提升为 an 时,上述问题的答案就得到了解决。 整数类型,uint64_t 符合该类型。

// 澄清编辑:

好的,但现在我又困惑了。具体来说,如果 uint8_t 是整数类型,那么为什么它会被提升为 int 呢?它似乎与常量 int 1 无关,如以下练习所示:

{
uint16_t t16 = 0;
uint8_t t8 = 0x80;
uint8_t t8_one = 1;
uint8_t t8_res;

t16 = (t8 << t8_one);
t8_res = (t8 << t8_one);

printf("t16: %x\n", t16);
printf(" t8: %x\n", t8_res);
}

t16: 100
 t8: 0

如果 uint8_t 是整数类型,为什么 (t8 << t8_one) 表达式会被提升?

--

作为参考,我正在使用 2005 年 5 月 6 日的 ISO/IEC 9899:TC9, WG14/N1124。如果该版本已过时,并且有人还可以提供指向更新版本的链接,我们也将不胜感激。

This question was first inspired by the (unexpected) results of this code:

uint16_t   t16 = 0;
uint8_t     t8 = 0x80;
uint8_t t8_res;

t16    = (t8 << 1);
t8_res = (t8 << 1);

printf("t16: %x\n", t16);    // Expect 0, get 0x100
printf(" t8: %x\n", t8_res); // Expect 0, get 0

But it turns out this makes sense:

6.5.7 Bitwise shift operators

Constraints

2 Each of the operands shall have integer type

Thus the originally confused line is equivalent to:

t16 = (uint16_t) (((int) t8) << 1);

A little non-intuitive IMHO, but at least well-defined.

Ok, great, but then we do:

{
uint64_t t64 = 1;
t64 <<= 31;
printf("t64: %lx\n", t64); // Expect 0x80000000, get 0x80000000
t64 <<= 31;
printf("t64: %lx\n", t64); // Expect 0x0, get 0x4000000000000000
}

// edit: following the same literal argument as above, the following should be equivalent:

t64 = (uint64_t) (((int) t64) << 31);

// hence my confusion / expectation [end_edit]

Now, we get the intuitive result, but not what would be derived from my (literal) reading of the standard. When / how does this "further automatic type promotion" take place? Or is there a limitation elsewhere that a type can never be demoted (that would make sense?), in that case, how do the promotion rules apply for:

uint32_t << uint64_t

Since the standard does say both arguments are promoted to int; should both arguments be promoted to the same type here?

// edit:

More specifically, what should the result of:

uint32_t t32 = 1;
uint64_t t64_one = 1;
uint64_t t64_res;

t64_res = t32 << t64_one;

// end edit

The answer to the above question is resolved when we recognize that the spec does not demand a promotion to int specifically, rather to an integer type, which uint64_t qualifies as.

// CLARIFICATION EDIT:

Ok, but now I am confused again. Specifically, if uint8_t is an integer type, then why is it being promoted to int at all? It does not seem to be related to the constant int 1, as the following exercise demonstrates:

{
uint16_t t16 = 0;
uint8_t t8 = 0x80;
uint8_t t8_one = 1;
uint8_t t8_res;

t16 = (t8 << t8_one);
t8_res = (t8 << t8_one);

printf("t16: %x\n", t16);
printf(" t8: %x\n", t8_res);
}

t16: 100
 t8: 0

Why is the (t8 << t8_one) expression being promoted if uint8_t is an integer type?

--

For reference, I'm working from ISO/IEC 9899:TC9, WG14/N1124 May 6, 2005. If that's out of date and someone could also provide a link to a more recent copy, that'd be appreciated as well.

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

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

发布评论

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

评论(3

九命猫 2024-12-19 21:56:09

我认为您困惑的根源可能是以下两个语句等效:

  • 每个操作数都应具有整数类型
  • 每个操作数应具有 int 类型

uint64_t 是整数类型。

I think the source of your confusion might be that the following two statements are not equivalent:

  • Each of the operands shall have integer type
  • Each of the operands shall have int type

uint64_t is an integer type.

热鲨 2024-12-19 21:56:09

§6.5.7 中的约束“每个操作数都应具有整数类型”。 是一个约束,意味着您不能在非整数类型(如浮点值或指针)上使用按位移位运算符。它不会导致您注意到的效果。

确实导致该效果的部分在下一段中:

 3.对每个操作数执行整数提升。结果的类型是提升后的左操作数的类型。

§6.3.1.1 中描述了整数促销

2.表达式中只要 int 就可以使用以下内容
或可以使用unsigned int

  • 具有整数类型的对象或表达式,其整数转换等级小于或等于 int 的等级并且
    无符号整数
  • _Boolintsigned intunsigned int 类型的位字段。

如果一个 int 可以表示原始类型的所有值,则该值
转换为 int;否则,它会被转换为无符号的
int。这些称为整数促销。所有其他类型都是
整数提升不变。

uint8_t 的等级低于 int,因此该值会转换为 int(因为我们知道 int > 必须能够表示 uint8_t 的所有值(考虑到这两种类型的范围要求)。

排名规则很复杂,但它们保证具有较高排名的类型不能具有较低的精度。这意味着,实际上,类型不能通过整数提升“降级”为精度较低的类型(uint64_t 可以提升为 int 或 < code>unsigned int,但当类型范围至少为 uint64_t 的范围时)。

在 uint32_t << 的情况下uint64_t,启动的规则是“结果的类型是提升的左操作数的类型”。因此,我们有几种可能性:

  • 如果 int 至少为 33 位,则 uint32_t 将提升为 int,结果将为 int;
  • 如果 int 小于 33 位且 unsigned int 至少为 32 位,则 uint32_t 将提升为 unsigned int code> 且结果将为 unsigned int
  • 如果 unsigned int 小于 32 位,则 uint32_t 将保持不变,结果将为 uint32_t

在当今常见的桌面和服务器实现中,intunsigned int 通常是 32 位,因此会出现第二种可能性(uint32_t 被提升为无符号整数)。过去,int / unsigned int 通常为 16 位,并且会出现第三种可能性(uint32_t 未提升)。

示例的结果:

uint32_t t32 = 1;
uint64_t t64_one = 1;
uint64_t t64_res;

t64_res = t32 << t64_one;

将值 2 存储到 t64_res 中。但请注意,这不会受到表达式结果不是 uint64_t 的影响 - 会受影响的表达式示例是:

uint32_t t32 = 0xFF000;
uint64_t t64_shift = 16;
uint64_t t64_res;

t64_res = t32 << t64_shift;

这里的结果是 <代码>0xf0000000。

请注意,虽然细节相当复杂,但您可以将其归结为您应该牢记的相当简单的规则:

在 C 中,算术永远不会在比 int 窄的类型中完成 /
无符号整数

The constraint in §6.5.7 that "Each of the operands shall have integer type." is a constraint that means you cannot use the bitwise shift operators on non-integer types like floating point values or pointers. It does not cause the effect you are noting.

The part that does cause the effect is in the next paragraph:

 3. The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand.

The integer promotions are described in §6.3.1.1:

 2. The following may be used in an expression wherever an int
or unsigned int may be used:

  • An object or expression with an integer type whose integer conversion rank is less than or equal to the rank of int and
    unsigned int.
  • A bit-field of type _Bool, int, signed int, or unsigned int.

If an int can represent all values of the original type, the value
is converted to an int; otherwise, it is converted to an unsigned
int
. These are called the integer promotions. All other types are
unchanged by the integer promotions.

uint8_t has a lesser rank than int, so the value is converted to an int (since we know that an int must be able to represent all the values of uint8_t, given the requirements on the ranges of those two types).

The ranking rules are complex, but they guarantee that a type with a higher rank cannot have a lesser precision. This means, in effect, that types cannot be "demoted" to a type with lesser precision by the integer promotions (it is possible for uint64_t to be promoted to int or unsigned int, but only if the range of the type is at least that of uint64_t).

In the case of uint32_t << uint64_t, the rule that kicks in is "The type of the result is that of the promoted left operand". So we have a few possibilities:

  • If int is at least 33 bits, then uint32_t will be promoted to int and the result will be int;
  • If int is less than 33 bits and unsigned int is at least 32 bits, then uint32_t will be promoted to unsigned int and the result will be unsigned int;
  • If unsigned int is less than 32 bits then uint32_t will be unchanged and the result will be uint32_t.

On today's common desktop and server implementations, int and unsigned int are usually 32 bits, and so the second possibility will occur (uint32_t is promoted to unsigned int). In the past it was common for int / unsigned int to be 16 bits, and the third possibility would occur (uint32_t left unpromoted).

The result of your example:

uint32_t t32 = 1;
uint64_t t64_one = 1;
uint64_t t64_res;

t64_res = t32 << t64_one;

Will be the value 2 stored into t64_res. Note though that this is not affected by the fact that the result of the expression is not uint64_t - and example of an expression that would be affected is:

uint32_t t32 = 0xFF000;
uint64_t t64_shift = 16;
uint64_t t64_res;

t64_res = t32 << t64_shift;

The result here is 0xf0000000.

Note that although the details are fairly intricate, you can boil it all down to a fairly simple rule that you should keep in mind:

In C, arithmetic is never done in types narrower than int /
unsigned int.

笔芯 2024-12-19 21:56:09

您在标准中发现了错误的规则:(相关内容类似于“通常的整数类型促销适用”。这就是第一个示例中您遇到的问题。如果像 uint8_t 这样的整数类型具有排名小于 int 的值将提升为 uint64_t,其排名不小于 int 或。 unsigned 所以没有促销执行并将 << 运算符应用于 uint64_t 变量

编辑: 所有小于 int 的整数类型。 code> 被提升为算术,这只是生活中的事实:) uint32_t 是否被提升取决于平台,因为它可能具有与 int 相同或更高的排名。代码>(未晋升)或更小的等级(晋升)。

对于 << 运算符,右侧操作数的类型并不重要,重要的是左侧操作数的位数(遵循上述规则)。对于正确的人来说,更重要的是它的价值。它不能为负数或超过(提升的)左操作数的宽度。

You found the wrong rule in the standard :( The relevant is something like "the usual integer type promotions apply". This is what hits you for the first example. If an integer type like uint8_t has a rank that is smaller than int it is promoted to int. uint64_t has not a rank that is smaller than int or unsigned so no promotion is performed and the << operator is applied to the uint64_t variable.

Edit: All integer types smaller than int are promoted for arithmetic. This is just a fact of life :) Whether or not uint32_t is promoted depends on the platform, because it might have the same rank or higher than int (not promoted) or a smaller rank (promoted).

Concerning the << operator the type of the right operand is not really important, what counts for the number of bits is the left one (with the above rules). More important for the right one is its value. It musn't be negative or exceed the width of the (promoted) left operand.

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