c++ 是否使用char[4]或char[8]作为int型常量?

bxjv4tth  于 2023-03-14  发布在  其他
关注(0)|答案(3)|浏览(220)

我有这些小函数,它们接受一个小的char数组,并将其重新转换为int或longlong:

#define HASH4(x) (*((int*)x))
#define HASH8(x) (*((longlong*)x))

int aValue=HASH4("FOOD");
longlong aBigValue=HASH8("SEXYCOOL");

我想知道在C/C++语法中是否有什么方法可以让我不需要#define就能完成这个任务?原因是,我想在case结构中使用这些,就像这样:

switch(something)
{
case HASH4("FOOD"): printf("Food!");break;
case HASH8("SEXYCOOL"): printf("Sexycool!");break;
}

......它不会让我这么做的。
那么,在c语法中有没有什么方法可以告诉它把这个4字节的char* 解释为int型的呢?或者,有没有什么方法可以写一个define来转换它呢?
澄清一下:我想知道在C/C++语法中是否有一种方法可以接受四个字节,这样这两个语句就等价了:

int something=Magic("\0\0\0A");
int something=65;

switch(stuff)
{
    case Magic("FOOD"): <-- becomes valid
}

...因为我们都知道const char“FOOD”只有四个字节,包含70,79,79,68...有没有什么方法可以 Package 一个漂亮的MAKEINT(4个字节),它完全由预处理器处理,或者与int或long long没有区别?

watbbzwu

watbbzwu1#

更新:@chtz的破解完全奏效了,它欺骗了编译器,让编译器没有意识到它正在从一个char数组构建一个int。

溶液1/4:使用宏通过手动计算4个字节来组合uint32_t

更新2:考虑字节序。x86-64系统是little-endian。我最初错误地使用了big-endian散列:

// For big-endian byte ordering
uint32_t num = ((chars[0]*256 + chars[1])*256 + chars[2])*256 + chars[3];

// Update 2: reverse the order for correct endianness:
// For little-endian byte ordering
uint32_t num = ((chars[3]*256 + chars[2])*256 + chars[1])*256 + chars[0];

测试cpp:

///usr/bin/env ccache g++ -Wall -Wextra -Werror -O3 -std=gnu++17 "$0" -o /tmp/a && /tmp/a "$@"; exit
// For the line just above, see my answer here: https://stackoverflow.com/a/75491834/4561887

#include <iostream>

#define HASH4(s) ((((s)[0]*256+(s)[1])*256+(s)[2])*256+(s)[3])

void check_int(int i)
{
    switch(i)
    {
    case HASH4("FOOD"):
        printf("FOOD\n");
        break;
    case HASH4("TREE"):
        printf("TREE\n");
        break;
    }
}

int main()
{
    std::cout << "Test\n";

    int something = HASH4("FOOD");
    printf("something = %i\n", something); // something = 1179602756
    check_int(something);

    something = 1179602756;
    check_int(something);

    // ----------------------------
    // withOUT using a #define now
    // ----------------------------

    something = ((('F'*256+'O')*256+'O')*256+'D');

    switch(something)
    {
        case ((('F'*256+'O')*256+'O')*256+'D'):
            printf("FOOD\n");
            break;
    }

    return 0;
}

运行命令:

chmod +x test.cpp   # make executable
./test.cpp          # run it

输出:

Test
something = 1179602756
FOOD
FOOD
FOOD

使用#define时 * 输出 *:这样做很好,因为((('F'*256+'O')*256+'O')*256+'D')是一个常量表达式!--它在编译时被完全计算为常量值。

溶液2/4(更好):使用常量表达式函数hack代替上面的宏hack

@Pepijn克雷默说得对,constexpr函数可以用来代替那些只做编译前计算的宏。换句话说,constexpr函数可以代替 * 一些 * 宏。constexpr函数可能是首选,因为它们具有类型安全和检查功能,并且避免了宏在传递表达式或赋值语句时的双重求值问题。
constexpr函数将在编译时 *(如果可能)计算为constexpr结果,否则在运行时 * 计算为常规结果。因此,它们就像是 * 一些 * 宏+常规函数的功能混合体。
这里有一个解决方案,将4个字符的std::array传入constexpr函数:

///usr/bin/env ccache g++ -Wall -Wextra -Werror -O3 -std=gnu++17 "$0" -o /tmp/a && /tmp/a "$@"; exit
// For the line just above, see my answer here: https://stackoverflow.com/a/75491834/4561887

#include <array>
#include <iostream>

constexpr uint32_t hash4chars(const std::array<char, 4>& chars)
{
    // For big-endian byte ordering
    // uint32_t num = ((chars[0]*256 + chars[1])*256 + chars[2])*256 + chars[3];

    // Update: reverse the order for correct endianness:
    // For little-endian byte ordering
    uint32_t num = ((chars[3]*256 + chars[2])*256 + chars[1])*256 + chars[0];
    return num;
}

void check_int(int i)
{
    switch(i)
    {
    case hash4chars({'F', 'O', 'O', 'D'}):
        printf("FOOD\n");
        break;
    case hash4chars({'T', 'R', 'E', 'E'}):
        printf("TREE\n");
        break;
    }
}

int main()
{
    std::cout << "Test\n";

    uint32_t num = hash4chars({'F', 'O', 'O', 'D'});
    printf("num = %u\n", num);
    check_int(num);

    // convert the num back to a char array to check that it was converted
    // correctly
    const char* str = (const char*)(&num);
    printf("%c%c%c%c\n", str[0], str[1], str[2], str[3]);

    return 0;
}

运行并输出,显示FOOD中的4个字节变成了1146048326uint32_t数字,而在我的x86-64 Linux系统上,这个数字又变成了4个字符FOOD(小字节序):

$ ./test.cpp 
Test
num = 1146048326
FOOD
FOOD

溶液3/4:(目前为止最好的)constexpr函数黑客使用std::string_view作为输入,而不是上面的std::array

更好的是,使用std::string_view作为输入参数,这样您仍然可以将原始的C字符串传递给它。

///usr/bin/env ccache g++ -Wall -Wextra -Werror -O3 -std=gnu++17 "$0" -o /tmp/a && /tmp/a "$@"; exit
// For the line just above, see my answer here: https://stackoverflow.com/a/75491834/4561887

#include <cstdint>
#include <iostream>
#include <string_view>

constexpr uint32_t hash4chars(const std::string_view& sv)
{
    // Error checking: ensure all inputs have only 4 chars.
    // Note: as really crude error checking, we'll just return the sentinel
    // value of `UINT32_MAX` if this error occurs. Better techniques exist
    if (sv.size() != 4)
    {
        printf("Error: the string view should be 4 chars long!\n");
        return UINT32_MAX;
    }

    // static_assert(sv.size() == 4); // doesn't work

    // For big-endian byte ordering
    // uint32_t num = ((sv[0]*256 + sv[1])*256 + sv[2])*256 + sv[3];

    // Update: reverse the order for correct endianness:
    // For little-endian byte ordering
    uint32_t num = ((sv[3]*256 + sv[2])*256 + sv[1])*256 + sv[0];
    return num;
}

void check_int(int i)
{
    switch(i)
    {
    case hash4chars("FOOD"):
        printf("FOOD\n");
        break;
    case hash4chars("TREE"):
        printf("TREE\n");
        break;
    }
}

int main()
{
    std::cout << "Test\n";

    uint32_t num = hash4chars("FOOD");
    printf("num = %u\n", num);
    check_int(num);

    // convert the num back to a char array to check that it was converted
    // correctly
    const char* str = (const char*)(&num);
    printf("%c%c%c%c\n", str[0], str[1], str[2], str[3]);

    return 0;
}

运行并输出(与之前完全相同):

$ ./test.cpp 
Test
num = 1146048326
FOOD
FOOD

溶液4/4:不要将4字节转换为整数;我只需要使用内置的C++散列函数直接散列字符串,作为字符串视图

基于您在问题中调用宏HASH4()HASH8()的事实,您似乎真的只需要输入字符串的唯一或接近唯一的哈希值?即:你实际上不需要转换它的等价空间整数表示相反,你只需要它的一部分。
在这种情况下,你也可以使用C++内置的std::hash<>{}()函数。

  1. https://en.cppreference.com/w/cpp/utility/hash-一般文档
  2. https://en.cppreference.com/w/cpp/string/basic_string_view/hash-有关it的std::string_view专门化的文档
  3. https://en.cppreference.com/w/cpp/string/basic_string_view/operator%22%22sv-operator""sv()函数的含义,用作"my_string"sv,以从上述示例中的C字符串"my_string"生成std::string_view
  • 但是 *,std::hash<>{}()不是一个constexpr函数,所以你也不能在switch的情况下使用它!相反,你必须使用ifelse风格的检查。

如何阅读std::hash<>{}()

  1. std是名称空间
  2. <>指定模板类型
  3. {}构造此类类型的默认对象
  4. ()调用operator()(类似于括号函数的[或“函子”]运算符;参见herehere),在本例中,该对象是对那些括号内的参数执行散列的函数。
    注:下面的代码工作得很好,可能是许多C人最喜欢的,但我发现它相当复杂,也许太“C"了。你的电话。它也不是一个constexpr表达式。我很高兴我终于达到了一个点,经过3年的日常C使用,我甚至可以阅读和编写这个自己,然而,并且能够访问C字符串的快速散列(解释为std::string_view s)实际上是 C 的一部分。
///usr/bin/env ccache g++ -Wall -Wextra -Werror -O3 -std=gnu++17 "$0" -o /tmp/a && /tmp/a "$@"; exit
// For the line just above, see my answer here: https://stackoverflow.com/a/75491834/4561887

#include <iostream>
#include <string_view>

void check_hash(std::size_t hash)
{
    if (hash == std::hash<std::string_view>{}(std::string_view{"FOOD", 4}))
    {
        printf("FOOD\n");
    }
    else if (hash == std::hash<std::string_view>{}(std::string_view{"TREE", 4}))
    {
        printf("TREE\n");
    }
}

int main()
{
    std::cout << "Test\n";

    std::size_t num
        = std::hash<std::string_view>{}(std::string_view{"FOOD", 4});
    printf("num = %lu\n", num);
    check_hash(num);

    return 0;
}

运行和输出:

$ ./test.cpp 
Test
num = 16736621008042147638
FOOD

std::hash函子非常难看,所以如果愿意,可以用一个宏来 Package 它:

#define HASH(string, num_chars) \
    std::hash<std::string_view>{}(std::string_view{(string), (num_chars)})

示例:

#include <iostream>
#include <string_view>

#define HASH(string, num_chars) \
    std::hash<std::string_view>{}(std::string_view{(string), (num_chars)})

void check_hash(std::size_t hash)
{
    if (hash == HASH("FOOD", 4))
    {
        printf("FOOD\n");
    }
    else if (hash == HASH("TREE", 4))
    {
        printf("TREE\n");
    }
}

int main()
{
    std::cout << "Test\n";

    std::size_t num = HASH("FOOD", 4);
    printf("num = %lu\n", num);
    check_hash(num);

    return 0;
}

输出与上面的相同。

更进一步

如果你想了解更多关于内存blob和字节数组之间的转换,也可以参考我在这里的其他回答:
1.如何在C中将struct变量转换为uint8_t数组:

  1. Answer 1/3: use a union and a packed struct
  2. Answer 2/3: convert a struct to an array of bytes via manual bit-shifting
  3. Answer 3/3: use a packed struct and a raw uint8_t pointer to it

需要考虑和理解的其他信息

要使4字节被解释为常量4字节int(const int32_t),只需使用

// this
#define CONST_INT32(bytes) (*((const int32_t*)(bytes)))

// instead of this
#define CONST_INT32(bytes) (*((int32_t*)(bytes)))

即:在指针转换之前添加const
但是,这会得到一个const int32_t,它与constexpr int32_t常量表达式int32_t * 不 * 相同。一个 * 常量表达式 * 告诉编译器,这段内存不会被忽视、编辑或重新解释-转换为另一种类型。事实上,通过宏将4个字节重新解释-转换为一个int已经违反了这一点。
所以,不,在C++中,我不知道有什么预处理器宏方法可以强制将4个字节解释为constexpr int
你可以将4个字节重新解释为const int,但这不是一回事,只有constexpr类型可以在switch语句中用作case,所以@dbush的答案是正确的,使用ifelse来检查const int的值。

注意:如果你声明了一个const int,编译器可能会看到它也可能是一个constexpr int,并为你做出决定。

#include <iostream>

int main()
{
    std::cout << "Test\n";

    const int CASE1 = 7; // compiler sees these could also be constexpr
    const int CASE2 = 8; // compiler sees these could also be constexpr

    int something = CASE1;

    switch(something)
    {
    case CASE1:
        printf("CASE1\n");
        break;
    case CASE2:
        printf("CASE2\n");
        break;
    }

    return 0;
}

......还有这个:

#include <iostream>

int main()
{
    std::cout << "Test\n";

    constexpr int CASE1 = 7; // you are explicitly making these constexpr
    constexpr int CASE2 = 8; // you are explicitly making these constexpr

    int something = CASE1;

    switch(something)
    {
    case CASE1:
        printf("CASE1\n");
        break;
    case CASE2:
        printf("CASE2\n");
        break;
    }

    return 0;
}
xtupzzrd

xtupzzrd2#

使用constexpr函数代替宏。例如

#include <string_view>
#include <utility>
#include <array>
#include <iostream>

// create a constexpr hash function which may
// be used both at compile and at runtime.
// I used std::string_view since I am used to that.
// but you can also use a char* and use strlen

static constexpr auto hash(const std::string_view& str)
{
    std::array<std::size_t, 4> primes{ 1,3,5,7 };
    std::size_t count = std::min(primes.size(), str.size());

    std::size_t hash{ 0ul };
    for (std::size_t n = 0; n < count; n++)
    {
        hash += (primes[n] * static_cast<std::size_t>(str[n]));
    }

    return hash;
}

int main()
{
    std::size_t something = hash("FOOD");

    switch (something)
    {
        case hash("FOOD"): std::cout << "Food!"; break;
        case hash("SEXYCOOL"): std::cout << "Sexycool!"; break;
    }

    return 0;
}
yshpjwxd

yshpjwxd3#

case标签的值必须是常量表达式,而像(*((int*)x))这样的表达式是不符合条件的。使用#define的事实并不重要。
为此,您需要使用if ... else链条。

if (something == HASH4("FOOD")) {
    printf("Food!");
} else if (something == HASH8("SEXYCOOL")) {
    printf("Sexycool!");
}

相关问题