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 ++中做到这一点。
		
		
- 
您可能应该在使用宏的参数时添加括号,如下所示:#define CLAMP255(v) ((v) > 255 ? 255 : ((v) < 0 ? 0 : (v)))
 
 
	  
这是C ++引入新的强制转换样式的原因之一,其中包括static_cast和reinterpret_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;  | 
 
		
		
- 
哇,很棒的帖子!是的,所以static_cast是我想用来安全的。我对你的样本中的指针有点困惑。我可以将一个带符号的char *指针转换为unsigned char *指针,然后安全地使用后一个指针读/写吗?通过安全,我的意思是从那一点开始我可以将后一个指针视为指向一个无符号字符数组?这将使我的代码更清晰,因为这意味着我不必来回转换。它似乎从快速测试开始,但我不确定它是否安全。
 
- 
是的,根据程序的语义,你可以安全地将一个带符号的字符数组转换为指向unsigned char的指针,你可以有效地说,这个内存不是一个有符号字符数组,而是一个无符号字符数组。但请注意,这将是一个reinterpret_cast,而不是静态演员,但从你描述问题的方式来看,我认为重新解释演员是你想要的。
 
- 
添加了一些关于数组的答案
 
- 
你有没有机会用std :: vector 数组知道一个不错的C ++方法?我尝试过这个,但它确实不起作用:std::vector buffer; std::vector cache = std::vector(reinterpret_cast(buffer.data()), reinterpret_cast(buffer.data() + buffer.size()));
 
- 
@serup为什么它不起作用?这对我来说可以。我会这样说的略有不同; std::vector buffer; unsigned char* ptr = reinterpret_cast(buffer.data()); std::vector cache(ptr, ptr + buffer.size());请注意,这将始终复制缓冲区,而普通数组方法则不会。
 
- 
@serup以下对我来说非常好,并且可以避免缓冲区的副本。我不是100%肯定,但是如果这可以保证符合标准。 std::vector buffer; std::vector& cache = reinterpret_cast&>(buffer);
 
- 
@wich,是的,我自己得出了这个结论,但是你说它会复制一下,还是会复制? - 我想没办法解决它
 
- 
@wich,如果你添加std :: move会怎么样?然后它会创建一个副本吗? 例如:char* buf = buffer.data();                                                        unsigned char* membuf = reinterpret_cast(buf);                     std::vector vec(std::move(membuf), std::move(membuf) + buffer.size());它有效,但我不确定它是否真的复制
 
- 
@serup它将复制所有内容,std::move仅在容器元素本身包含指向其他内存的指针的情况下才有用。 在这种情况下,指向内存不重复,而是"移动"而不是。 对于基本类型,如char,int,float等,它只是一个普通的副本。
 
- 
尼斯。 谢谢...
 
 
	  
是的,这是安全的。
c语言使用称为整数提升的功能来在执行计算之前增加值中的位数。因此,您的CLAMP255宏将以整数(可能是32位)精度运行。结果被分配给jbyte,这会将整数精度降低到适合jbyte的8位。
		
		
- 
你可以评论一下发生了什么事吗? signed char的值为-100。我很困惑这个值被转换成什么,什么被转换回来,如果这是安全的。
 
- 
你从-100开始,这是二进制的10011100。将其转换为unsigned char,结果为156.这是用于计算的值(添加30然后测试<0或> 255)。你将得到186(10111011二进制),它被转换回一个有符号的char,值为-70。无论如何,这一切都适合8位数学。
 
- 
如果你开始使用-1(11111111二进制),然后将其转换为无符号字符,则得到255.如果你加30,那么你得到285.如果这是用8位数学执行的(即没有整数提升)它会溢出并具有值29.然后它将在0-255的范围内,因此不会被钳制。由于我们有整数提升,我们有足够的精度来表示285,因此(v> 255)测试将为真,并且该值将被限制为255。
 
- 
@rebecca你的代码实际上从未看到数字-100,带有像素值-100的表达式(unsigned char)pixel已经给你一个值156。
 
 
	  
 
你是否意识到,对于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正确地认为是无操作。
		
		
- 
你是说我正在做的演员安全或不安全吗?
 
- 
从C标准的律师角度来看,演员阵容有些不安全,但在大多数常见系统(8位字符和2位补码算术的机器)上都足够安全,因为我知道没有编译器实现会在这里做错事(尽管MSVC会如果启用了整数转换溢出检查,则在此处生成运行时警告)。
 
- 
好吧,这对于任何不对签名字符使用二进制补码的架构都会出错,无论签名号码实现如何,简单转换都可以。
 
- 
@wich:我很困惑。你是说我的样本代码是否是以安全的方式处理事情的正确方法?
 
- 
:是的,不是。"添加256"策略适用于之前已经错误解释数据的情况。