为什么 C++ 模运算符 return 0 for -1 % str.size()?

Why does the C++ modulo operator return 0 for -1 % str.size()?

我很困惑为什么以下代码会产生此输出:

#include <iostream>
#include <string>

using namespace std;

int main()
{
    int i = -1;
    string s = "abc";
    int j = s.size();
    int x = 1 % 3;
    int y = i % j; 
    int z = i % s.size(); 
    cout << s.size() << endl; // 3
    cout << x << endl;        // 1
    cout << y << endl;        // -1
    cout << z << endl;        // 0
}

为什么 z = 0?跟选角有关系吗?

这里到底发生了什么:

int z = i % s.size();

i转换为size_t,因为对方s.size()size_t。与 int 不同的是,size_t 是无符号的;也就是说该值被解释为正数。

检查这一行的输出:

std::cout << (size_t)-1 << std::endl;

看看-1变成了什么。

@GhaziMajdoub 的回答是正确的,但是 - 你为什么不让编译器告诉你发生了什么?

让我们使用 Flags to enable thorough and verbose g++ warnings ...

$ g++ -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy \ 
-Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations \
-Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual \
-Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-null-sentinel \
-Wstrict-overflow=5 -Wswitch-default -Wundef -Werror -Wno-unused -o a a.cpp
a.cpp: In function ‘int main()’:
a.cpp:12:13: error: conversion to ‘std::__cxx11::basic_string<char>::size_type’ {aka
‘long unsigned int’} from ‘int’ may change the sign of the result
[-Werror=sign-conversion]
   12 |     int z = i % s.size();
      |             ^
cc1plus: all warnings being treated as errors

a.cpp: In function ‘int main()’:
a.cpp:12:13: warning: conversion to ‘std::__cxx11::basic_string<char>::size_type’ 
{aka ‘long unsigned int’} from ‘int’ may change the sign of the result [-Wsign-
conversion]
   12 |     int z = i % s.size();
      |             ^

你看到了:i 被转换为 long unsigned int,所以它不再是 -1

因此,将您的代码精简为 最小 示例,您会问为什么会打印 0:

#include <iostream>
#include <string>

int main()
{
    int a = -1;
    std::string::size_type b = 3; 
    int c = a % b;
    std::cout << c << '\n';
}

这里主要的操作是这样的:

a % b

按照标准,

5.6 乘法运算符[expr.mul]

  1. The operands of * and / shall have arithmetic or unscoped enumeration type; the operands of % shall have integral or unscoped enumeration type. The usual arithmetic conversions are performed on the operands and determine the type of the result.

那么..那些"usual arithmetic conversions"呢?这是为了在执行实际操作之前将两个操作数的类型匹配到一个公共类型。以下被认为顺序

  • 如果两个操作数都是整数,则首先对两个操作数执行整数提升。如果在整数提升后操作数仍然具有不同的类型,则转换继续如下:
    • 如果一个操作数的无符号类型 T 的转换等级至少与另一个操作数的类型一样高,则另一个操作数将转换为类型 T。
    • 否则,一个操作数的带符号类型 T 的转换等级高于另一个操作数的类型。仅当类型 T 能够表示其先前类型的所有值时,另一个操作数才转换为类型 T。
    • 否则,两个操作数都转换为与有符号类型T对应的无符号类型。

这是很多的合法化,因为有效地说:

  • 你有两个操作数,一个signed int和一个std::string::size_type
  • std::string::size_type的排名signed int
  • 因此,signed int 操作数转换为类型 std::string:size_type 被请求的操作之前。

那么就剩下转换了,也就是多了一个legalize:

4.7 积分转换 [conv.integral]

  1. If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2n where n is the number of bits used to represent the unsigned type). [Note: In a two’s complement representation, this conversion is conceptual and there is no change in the bit pattern (if there is no truncation). —end note]

这意味着,在 32 位 std::string::size_type 平台上,您将获得 232-1 作为 int 的转换值(-1).

这意味着……

4294967295 % 3

这是...。如果 std::string::size_type 是 64 位,那么上面的所有内容都保持不变,除了最后的计算,即:

18446744073709551615 % 3