我正在使用JNI并且有一个jbyte类型的数组,其中jbyte表示为带符号的char,即-128到127.jbytes表示图像像素。对于图像处理,我们通常希望像素分量的范围为0到255.因此我想将jbyte值转换为0到255的范围(即与unsigned char相同的范围),对值进行一些计算然后存储再次以jbyte结果。
如何安全地进行这些转换?
我设法让这段代码工作,其中像素值增加30但是被限制为255,但我不明白它是安全还是便携:
#define CLAMP255(v) (v > 255 ? 255 : (v < 0 ? 0 : v))
jbyte pixel = ...
pixel = CLAMP_255((unsigned char)pixel + 30);
我很想知道如何在C和C ++中执行此操作。
答案 0 :(得分:103)
这是C ++引入新演员风格的原因之一,其中包括static_cast
和reinterpret_cast
通过说明从有符号转换为无符号可能意味着两件事你可能意味着你希望无符号变量包含有符号变量的值,模数为无符号类型的最大值+ 1.这就是你的签名char的值为-128,然后为值{128添加CHAR_MAX+1
,如果值为-1,则为{255}添加CHAR_MAX+1
,这是由的static_cast。另一方面,您可能意味着将某个变量引用的内存的位值解释为无符号字节,而不管系统上使用的有符号整数表示,即它是否具有位值0b10000000
它对于位值0b11111111
,应该计算值128和255,这是通过reinterpret_cast实现的。
现在,对于二进制补码表示,这恰好是完全相同的事情,因为-128表示为0b10000000
,-1表示为0b11111111
,同样适用于它们之间的所有。然而,其他计算机(通常是较旧的架构)可能使用不同的签名表示,例如符号和数字或补码。在'补码中,0b10000000
位值不是-128,而是-127,所以对无符号字符的静态强制转换会产生129,而reinterpret_cast会使这个为128.另外在'补充{{1 bitvalue不是-1,而是-0,(是的,这个值存在于'的补码中),并且将使用static_cast转换为值0,但使用reinterpret_cast将值转换为255。请注意,对于1的补码,无符号值128实际上不能用有符号的char表示,因为它的范围是-127到127,因为-0值。
我不得不说绝大多数计算机都会使用两个补码,这使得整个问题几乎无法运行。你可能只会在非常古老的架构中看到除了两个补码以外的系统,想想'60年代的时间框架。
语法归结为以下内容:
0b11111111
使用数组以一种不错的C ++方式执行此操作:
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方式:
jbyte memory_buffer[nr_pixels];
unsigned char* pixels = reinterpret_cast<unsigned char*>(memory_buffer);
答案 1 :(得分:2)
是的,这是安全的。
c语言在执行计算之前使用称为整数提升的功能来增加值中的位数。因此,您的CLAMP255宏将以整数(可能是32位)精度运行。结果被分配给一个jbyte,它将整数精度降低到适合jbyte的8位。
答案 2 :(得分:1)
你是否意识到,CLAMP255为v&lt; 0返回0 0> 255,v> = 0?
恕我直言,CLAMP255应定义为:
#define CLAMP255(v) (v > 255 ? 255 : (v < 0 ? 0 : v))
差异:如果v不大于255且不小于0:返回v而不是255
答案 3 :(得分:0)
有两种方法可以解释输入数据; -128是最低值,127是最高值(即真正的有符号数据),或0是最低值,127是中间的某个地方,下一个“更高”数字是-128,其中-1是“最高”值(也就是说,最重要的位已被误解为二进制补码表示中的符号位。
假设你的意思是后者,那么正式的方法是
signed char in = ...
unsigned char out = (in < 0)?(in + 256):in;
至少gcc正确识别为无操作。
答案 4 :(得分:0)
我不是100%确定我理解你的问题,所以告诉我,如果我错了。
如果我做对了,你正在读取技术签名字符的jbytes,但真的像素值范围从0到255,你想知道你是怎么做的应该处理它们而不破坏过程中的值。
然后,您应该执行以下操作:
在执行任何其他操作之前将jbytes转换为unsigned char,这将绝对恢复您尝试操作的像素值
在进行中间计算时使用更大的有符号整数类型,例如int,这样可以确保检测到并处理上溢和下溢(特别是不转换为签名类型可能会强制编译器将每个类型提升为无符号类型,在这种情况下,您将无法在以后检测到下溢)
当分配回jbyte时,你想要将你的值钳位到0-255范围,转换为unsigned char然后再转换为signed char:我不确定第一次转换是否严格必要的,但如果你同时做到这两件事,那就不错了
例如:
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);