在c++中将int的最低有效位放入char中

Place least significant bits of int into char in C++

本文关键字:有效位 char c++ 中将 int      更新时间:2023-10-16

我想找到一种最有效的方法来计算包含c++ 11中int的最低有效位的char。解决方案必须与任何可能的标准兼容的编译器一起工作。(我使用的是N3290 c++草案规范,本质上是c++ 11)

这样做的原因是我正在编写类似于模糊测试器的东西,并且想要检查需要std::string作为输入的库。所以我需要为字符串生成随机字符。我使用的伪随机生成器提供了整数,其低位是均匀随机的,但我不确定确切的范围。(基本上确切的范围取决于"测试用例的大小"运行时参数。)

如果我不关心在任何编译器上工作,这将像这样简单:

inline char int2char(int i) { return i; }

在你认为这是一个微不足道的问题之前,考虑一下:

  • 您不知道char是有符号类型还是无符号类型

  • 如果char是有符号的,那么从不可表示的intchar的转换是"实现定义的"(§4.7/3)。这比undefined好得多,但是对于这个解决方案,我需要看到一些证据,证明标准禁止将所有不在CHAR_MINCHAR_MAX之间的整数转换为''

  • reinterpret_cast不允许在有符号和无符号字符之间使用(§5.2.10)。static_cast执行与前一点相同的转换

  • char c = i & 0xff;——尽管它沉默了一些编译器警告——几乎肯定不是对所有实现定义的转换都是正确的。特别是,i & 0xff总是一个正数,因此在c有符号的情况下,很可能不会将i的负值转换为c的负值。

这里有一些确实有效的解决方案,但在大多数情况下,我担心它们不会像简单的转换那样有效。对于这么简单的东西来说,这些看起来也太复杂了:

  • 在指针或引用上使用reinterpret_cast,因为您可以从unsigned char *unsigned char &转换为char *char &(但可能以运行时开销为代价)。

  • 使用charunsigned char的联合,首先将int分配给unsigned char,然后提取char(这再次可能会更慢)。

  • 向左和向右移动以对int进行符号扩展。例如,如果i是int,运行c = ((i << 8 * (sizeof(i) - sizeof(c)) >> 8 * (sizeof(i) - sizeof(c))(但这是不优雅的,如果编译器没有优化移位,相当慢)。

下面是一个最小的工作示例。目的是论证断言在任何编译器上都不会失败,或者定义一个替代的int2char,其中断言永远不会失败。

#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstdlib>
using namespace std;
constexpr char int2char(int i) { return i; }
int
main(int argc, char **argv)
{
  for (int n = 1; n < min(argc, 127); n++) {
    char c = -n;
    int i = (atoi(argv[n]) << 8) ^ -n;
    assert(c == int2char(i));
  }
  return 0;
}

我用c++来表达这个问题,因为在网上更容易找到标准,但我对C的解决方案同样感兴趣。下面是C中的MWE:

#include <assert.h>
#include <stdlib.h>
static char int2char(int i) { return i; }
int
main(int argc, char **argv)
{
  for (int n = 1; n < argc && n < 127; n++) {
    char c = -n;
    int i = (atoi(argv[n]) << 8) ^ -n;
    assert(c == int2char(i));
  }
  return 0;
}

一个更好的方法是有一个字符数组,并生成一个随机数从该数组中选择一个字符。这样你就会得到"表现良好"的角色;或者至少是有明确定义的坏的角色。如果你真的想要所有256个字符(注意8位假设),那么创建一个包含256个条目的数组('a','b',....'t','n'.....)

这也是可移植的

鉴于您似乎对值(而不是数字值)感兴趣,并且还要求C解决方案,我将发布我认为是兼容和最佳的东西:

inline char int2char(int i) {
    char ret;
    memcpy(&ret, (char *)&i + OFFSET, 1);
    return ret;
}

其中OFFSET是宏,根据端序检查扩展为0sizeof(int)-1

AFAICS,无论char是有符号的还是无符号的,用什么表示负数,或者charint的宽度,这都是不变的。它不依赖于任何奇怪的类型双关语技巧,也没有分支或复杂的操作(如除法)。

我说"最佳",因为我假设任何理智的编译器都将memcpy视为内在的,因此会在这里做一些聪明的事情。