关于c ++:从signed char转换为unsigned char并再次返回?

Converting from signed char to unsigned char and back again?

我正在使用JNI并且有一个jbyte类型的数组,其中jbyte表示为有符号的char,即-128到127.jbytes表示图像像素。 对于图像处理,我们通常希望像素分量的范围为0到255.因此我想将jbyte值转换为0到255的范围(即与unsigned char相同的范围),对值进行一些计算然后存储 结果再次作为jbyte。

如何安全地进行这些转换?

我设法让这段代码工作,其中一个像素值增加30但是被限制为255,但我不明白它是安全的还是可移植的:

1
2
3
4
 #define CLAMP255(v) (v > 255 ? 255 : (v < 0 ? 0 : v))

 jbyte pixel = ...
 pixel = CLAMP_255((unsigned char)pixel + 30);

我很想知道如何在C和C ++中做到这一点。


这是C ++引入新的强制转换样式的原因之一,其中包括static_castreinterpret_cast

你可以通过说明从有符号到无符号的转换来表示两件事,你可能意味着你希望无符号变量包含有符号变量的值,模数为无符号类型的最大值+ 1.这就是你的签名字符有一个如果值为-128,则为CHAR_MAX+1添加值128,如果值为-1,则添加CHAR_MAX+1值为255,这是static_cast所做的。另一方面,您可能意味着将某个变量引用的内存的位值解释为无符号字节,而不管系统上使用的有符号整数表示,即它是否具有位值0b10000000,它应该评估值为128,对于位值0b11111111为255,这是通过reinterpret_cast实现的。

现在,对于二进制补码表示,这恰好是完全相同的事情,因为-128表示为0b10000000,-1表示为0b11111111,同样适用于它们之间的所有。然而,其他计算机(通常是较旧的架构)可能使用不同的签名表示,例如符号和数字或补码。在'补码中,0b10000000比特值不是-128,而是-127,因此对无符号字符的静态强制转换将使这个为129,而reinterpret_cast将使这为128.另外在补充0b11111111比特值时不是-1,而是-0,(是的,这个值存在于'补码中),并且将使用static_cast转换为值0,但使用reinterpret_cast将值转换为255。请注意,在1的补码的情况下,无符号值128实际上不能用有符号的char表示,因为它的范围是-127到127,因为-0值。

我不得不说绝大多数计算机都会使用两个补码,这使得整个问题几乎无处不在。你可能只会在非常古老的架构中看到除了两个补码以外的系统,想想60年代的时间框架。

语法归结为以下内容:

1
2
3
4
5
6
7
signed char x = -100;
unsigned char y;

y = (unsigned char)x;                    // C static
y = *(unsigned char*)(&x);               // C reinterpret
y = static_cast<unsigned char>(x);       // C++ static
y = reinterpret_cast<unsigned char&>(x); // C++ reinterpret

要用一个很好的C ++方式用数组做到这一点:

1
2
jbyte memory_buffer[nr_pixels];
unsigned char* pixels = reinterpret_cast<unsigned char*>(memory_buffer);

或C方式:

1
unsigned char* pixels = (unsigned char*)memory_buffer;


是的,这是安全的。

c语言使用称为整数提升的功能来在执行计算之前增加值中的位数。因此,您的CLAMP255宏将以整数(可能是32位)精度运行。结果被分配给jbyte,这会将整数精度降低到适合jbyte的8位。


你是否意识到,对于v <0,CLAMP255返回0,对于v> = 0,CLAMP255返回255?
恕我直言,CLAMP255应定义为:

1
#define CLAMP255(v) (v > 255 ? 255 : (v < 0 ? 0 : v))

差异:如果v不大于255且不小于0:返回v而不是255


我不是百分百肯定我理解你的问题,所以告诉我,如果我错了。

如果我做对了,你正在读取技术上签名的字符jbytes,但实际上像素值的范围是0到255,你想知道如何处理它们而不破坏过程中的值。

然后,您应该执行以下操作:

  • 在执行任何其他操作之前将jbytes转换为unsigned char,这将绝对恢复您尝试操作的像素值

  • 在进行中间计算时使用较大的有符号整数类型(例如int),以确保可以检测和处理上溢和下溢(特别是,不转换为有符号类型可能会强制编译器将每种类型提升为无符号类型,在这种情况下,您将无法在以后检测到下溢)

  • 当分配回jbyte时,你需要将你的值钳位到0-255范围,转换为unsigned char然后再转换为signed char:我不确定第一次转换是否是必要的,但你可以如果你们两个都做错了,那就错了

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
inline int fromJByte(jbyte pixel) {
    // cast to unsigned char re-interprets values as 0-255
    // cast to int will make intermediate calculations safer
    return static_cast<int>(static_cast<unsigned char>(pixel));
}

inline jbyte fromInt(int pixel) {
    if(pixel < 0)
        pixel = 0;

    if(pixel > 255)
        pixel = 255;

    return static_cast<jbyte>(static_cast<unsigned char>(pixel));
}

jbyte in = ...
int intermediate = fromJByte(in) + 30;
jbyte out = fromInt(intermediate);

有两种方法可以解释输入数据; -128是最低值,127是最高值(即真正的有符号数据),或0是最低值,127是中间的某个地方,下一个"更高"数字是-128,其中-1是"最高"值(也就是说,最重要的位已被误解为二进制补码表示法中的符号位。

假设你的意思是后者,那么正式的方法是

1
2
signed char in = ...
unsigned char out = (in < 0)?(in + 256):in;

至少gcc正确地认为是无操作。