如何在C语言中将有符号整数转换为相应的无符号整数?

3

我想定义一个C宏

#define TO_UNSIGNED(x) (...)

函数 unsigned_cast 接受一个有符号整数 x(可以是: signed charshortintlonglong long 或者其他任何长度甚至比 long long 还长的类型),并将 x 转换为相同大小的无符号整数。

假设有符号整数使用二进制补码表示,则可以将任何值(正数或负数)转换为其二进制补码表示,然后将其解释为相同大小的无符号整数。

我假设使用了一个合理的现代优化编译器,该编译器可以消除未使用的分支。例如,如果执行 sizeof(X) < 4 ? f(Y) : g(Z),则不会评估 X,并且只生成和评估 f(Y)g(Z) 中的一个。


应该取x的二进制补码表示,并将其解释为相同大小的无符号整数。 - pts
@pts,好的,已添加答案。使用三元运算符代替了。 - Leeor
1
我不相信这是可能的。在预处理器中,您无法访问类型信息,也无法使用 sizeof - Paul Hankin
1
@chux:我的第一个用例是 #define ADD_WRAP(x, y) ((typeof(x))(TO_UNSIGNED(x) + TO_UNSIGNED(y))),它类似于使用 gcc -fwrapv#define ADD_WRAP(x, y) ((x) + (y)),即与无符号类型相同的整数环绕。 - pts
1
@pts:我认为在这种特殊情况下,重要的是在预处理之后编译器对 sizeof 进行评估。如果预处理器可以将 sizeof 评估为十进制数,则可以使用连接运算符 ##TO_UNSIGNED(x) 转换为 TO_UINT1(x)TO_UINT2(x) 等等。使用 sizeof 并依赖于优化编译器来删除常量 false 条件的死分支的解决方案不符合您的类型要求。匿名人士是正确的:预处理器中没有 sizeof - M Oehm
显示剩余9条评论
4个回答

7

我来试试,但我必须说这更多是宏编程的精神,而不是因为我认为这样的宏很有用。下面开始:

#include <stdlib.h>
#include <stdio.h>

#define TO_UNSIGNED(x) (                                            \
    (sizeof(x) == 1)                ? (unsigned char) (x) :         \
    (sizeof(x) == sizeof(short))    ? (unsigned short) (x) :        \
    (sizeof(x) == sizeof(int))      ? (unsigned int) (x) :          \
    (sizeof(x) == sizeof(long))     ? (unsigned long) (x) :         \
                                      (unsigned long long) (x)      \
    )

// Now put the macro to use ...

short minus_one_s()
{
    return -1;
}

long long minus_one_ll()
{
    return -1LL;
}

int main()
{
    signed char c = -1;
    short s = -1;
    int i = -1;
    long int l = -1L;
    long long int ll = -1LL;

    printf("%llx\n", (unsigned long long) TO_UNSIGNED(c));
    printf("%llx\n", (unsigned long long) TO_UNSIGNED(s));
    printf("%llx\n", (unsigned long long) TO_UNSIGNED(i));
    printf("%llx\n", (unsigned long long) TO_UNSIGNED(l));
    printf("%llx\n", (unsigned long long) TO_UNSIGNED(ll));

    printf("%llx\n", (unsigned long long) TO_UNSIGNED(minus_one_s()));
    printf("%llx\n", (unsigned long long) TO_UNSIGNED(minus_one_ll()));

    return 0;
}

该宏使用三元比较运算符?:来模拟一个适用于所有已知带符号整数大小的switch语句。(这应该也能捕获适当的无符号整数和从<stdint.h>typedef的类型。它可以使用表达式。它也接受浮点数,尽管不完全是我期望的方式。)
有些混乱的printf显示负数被扩展为源整数的本机大小。 编辑:原作者正在寻找一个返回与源类型相同长度的无符号类型表达式的宏。上述宏并没有做到这一点:因为三元比较的两个备选值都被提升为通用类型,所以宏的结果将始终是最大大小的类型,即unsigned long long
不同类型的分支可能可以通过纯宏解决方案实现,使得在预处理之后,编译器只看到一个类型,但是预处理器不知道类型,因此不能在这里使用sizeof,这排除了这样的宏。
但是为了(虚弱地)为自己辩护,我会说如果该宏生成的无符号长长整型结果的值被赋给适当的无符号类型(例如short的unsigned short),则该值应该永远不会被截断,因此该宏可能有某些用途。 编辑 II:现在我在另一个问题中意外发现了C11的_Generic关键字(并安装了支持它的编译器),我可以提供一个有效的解决方案:下面的宏真正返回具有正确类型的正确值:
#define TO_UNSIGNED(x) _Generic((x),           \
    char:        (unsigned char) (x),          \
    signed char: (unsigned char) (x),          \
    short:       (unsigned short) (x),         \
    int:         (unsigned int) (x),           \
    long:        (unsigned long) (x),          \
    long long:   (unsigned long long) (x),     \
    default:     (unsigned int) (x)            \
    )
_Generic 选择在编译时解析,没有产生过大的int类型中间结果的开销。(一个真实的宏应该包括无符号类型本身作为null-cast。另外请注意,我必须显式地包含signed char,否则仅使用char将不起作用,即使我的chars是有符号的。)需要一个实现C11或至少其_Generic关键字的最新编译器,这意味着这个解决方案并不是非常可移植,可以参考这里

2
你的 TO_UNSIGNED 宏返回值的类型是什么?返回类型始终相同(unsigned long long)。但我明确要求一个根据输入类型而有不同返回类型的宏。因此,这不是我的问题的解决方案。 - pts
@pts 返回类型取决于进入的“switch”情况。例如,对于源类型为“short”,“unsigned short”和“int16_t”,它是unsigned short。在printf语句中强制转换为unsigned long long只是为了可以使用%llx说明符打印所有情况。(需要进行强制转换,因为printf的参数被视为可变参数,即大小不超过int的所有内容都会提升为int,其余内容则以自己的类型传递。) - M Oehm
@Pitarou 是的,你说得对。我通过打印结果的大小进行了检查。我认为三元比较的两个分支被提升为相同的类型。 - M Oehm
@MOehm,如我在下面评论中提到的 - 我完全同意这里的主要价值将在于根据类型接收正确的2进制补码转换(至少在OP未被证明否认之前..),我仍然不明白将其强制转换为unsigned long long会如何干扰任何实际用途。 - Leeor
1
@pts:啊,优雅。如果你有一个支持C11的编译器,你可以使用_Generic来实现你的宏:没有整数膨胀,在编译时进行类型评估;请参见我的编辑答案。否则,我恐怕只能使用老旧、生锈的C语言了——优雅可见鬼了。;-) - M Oehm
显示剩余7条评论

3
您不需要使用宏,自动转换即可完成。例如:
int x = -1;
unsigned int y;

y = x;

编辑

您似乎希望有一个宏可以从变量名称中推断出其类型。这是不可能的。宏在编译的一个阶段运行,编译器没有可用的类型信息。因此,无论变量的类型如何,宏都必须发出相同的代码。

当类型信息可用时,编译器将坚持每个表达式具有一致的类型。但您要求的代码是不一致的类型。

最好的方法是自己提供类型信息。例如:

#define TO_UNSIGNED(type, name) (unsigned type(name))

1
我明确地要求一个宏,因此这并没有回答我的问题。宏的整个重点在于它足够聪明,可以自动找出“unsigned int”的类型。 - pts
1
这需要显式地传递类型。 - Leeor
1
确实,使用一个带有两个参数的宏很容易解决这个问题。但我需要一个带有一个参数的宏,并且它应该足够智能,可以自动推断出类型。 - pts
你认为这是不可能的理由是错误的。宏体可以包含一个表达式,根据输入类型而表现出不同的行为。例如,#define ADD(a, b) ((a) + (b)) 的行为是不同的:它可以是 int 加法、unsigned 加法、unsigned long 加法等等。在宏扩展时不需要类型信息就可以使其工作。 - pts
谢谢你的帮助。不幸的是,我不能接受你的答案,因为它没有解决问题,也没有提供有效的证明表明这个问题无法解决。 - pts

2

好的,既然您打算使用这个宏将负值隐式转换为它们的二进制补码形式,我认为我们可以按照以下方式进行处理:

#include "stdio.h"
#include "stdint.h"


#define TO_UNSIGNED(x) ( \
                          (sizeof(x) == 1 ? (uint8_t)x : \
                          (sizeof(x) <= 2 ? (uint16_t)x : \
                          (sizeof(x) <= 4 ? (uint32_t)x : \
                          (sizeof(x) <= 8 ? (uint64_t)x : \
                          x \
                        )))))



int main () {
    char a = -4;
    int b = -4;

    printf ("TO_UNSIGNED(a) = %u\n", TO_UNSIGNED(a));
    printf ("TO_UNSIGNED(b) = %u\n", TO_UNSIGNED(b));
    return 0;
}

输出:

TO_UNSIGNED(a) = 252
TO_UNSIGNED(b) = 4294967292

当然可能需要支持更长的长度,我现在将> 64位留下来,只返回x本身。

你的 TO_UNSIGNED 宏返回值的类型是什么?返回类型始终相同(uint64_t)。但我明确要求一个根据输入类型而有不同返回类型的宏。因此,这不是我的问题的解决方案。 - pts
这对于比 long long 更长的类型不起作用。 - pts
@pts 是一个宏,返回类型与三元运算符返回的类型完全相同,根据大小而定。关于 > long long 的问题是正确的,您需要选择如何实现这些值。 - Leeor
1
@pts:那么请解释一下您计划如何使用它,对于大多数情况下,根据您使用此宏的方式,结果将被转换,只要您正确获取2的补码值,就不应该有任何问题,据我所见。 - Leeor
谢谢你的帮助。不幸的是,我无法接受你的答案,因为它没有解决问题:它没有将输入转换为相应的无符号类型。 - pts
显示剩余2条评论

0

看起来没有通用的解决方案能够支持所有可能大小的整数。

对于一个硬编码类型列表,我能够使用C语言中的__builtin_choose_expr和C++中的重载函数使其工作。这是解决方案: https://github.com/pts/to-unsigned/blob/master/to_unsigned.h

相关的C代码如下:

#define TO_UNSIGNED(x) ( \
    __builtin_choose_expr(__builtin_types_compatible_p(__typeof(x), unsigned char), (unsigned char)(x), \
    __builtin_choose_expr(__builtin_types_compatible_p(__typeof(x), char), (unsigned char)(x), \
    __builtin_choose_expr(sizeof(x) == sizeof(char), (unsigned char)(x), \
    __builtin_choose_expr(__builtin_types_compatible_p(__typeof(x), unsigned short), (unsigned short)(x), \
    __builtin_choose_expr(__builtin_types_compatible_p(__typeof(x), short), (unsigned short)(x), \
    __builtin_choose_expr(sizeof(x) == sizeof(short), (unsigned short)(x), \
    __builtin_choose_expr(__builtin_types_compatible_p(__typeof(x), unsigned), (unsigned)(x), \ 
    __builtin_choose_expr(__builtin_types_compatible_p(__typeof(x), int), (unsigned)(x), \
    __builtin_choose_expr(sizeof(x) == sizeof(int), (unsigned)(x), \
    __builtin_choose_expr(__builtin_types_compatible_p(__typeof(x), unsigned long), (unsigned long)(x), \
    __builtin_choose_expr(__builtin_types_compatible_p(__typeof(x), long), (unsigned long)(x), \
    __builtin_choose_expr(sizeof(x) == sizeof(long), (unsigned long)(x), \
    __extension__ __builtin_choose_expr(__builtin_types_compatible_p(__typeof(x), unsigned long long), (unsigned long long)(x), \
    __extension__ __builtin_choose_expr(__builtin_types_compatible_p(__typeof(x), long long), (unsigned long long)(x), \
    __extension__ __builtin_choose_expr(sizeof(x) == sizeof(long long), (unsigned long)(x), \
    (void)0)))))))))))))))) 

在 C11 中,可以使用等效的 _Generic 结构替代 __builtin_choose_expr + __builtin_types_compatible_p,前提是编译器支持它。

C++11 有 std::make_unsigned,并且其在 libstdc++ 中的实现明确枚举了它所知道的整数类型,类似于我对 TO_UNSIGNED 的 C++ 实现。


所有这些似乎都是毫无意义的。如果您有一个已知类型需要转换为有符号类型,那么您只需执行此操作即可。试图编写允许您不知道正在使用哪些数据类型的代码似乎对我来说是一个巨大的警告信号。"我不知道这是什么,但我要将其转换为其他东西"具有非常糟糕的代码气味。 - Andrew Henle
1
@AndrewHenle 这正是泛型代码的含义吧?我的意思是“试图编写允许您不知道正在使用哪些数据类型的代码”部分。 - Tamás Szelei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接