Char C 有关编码有符号/无符号的问题

11

我读到C语言没有定义char是有符号还是无符号的,而在GCC页面上写道,在x86上可以是有符号的,在PowerPPC和ARM上可能是无符号的。

好的,我正在使用GLIB编写程序,将char定义为gchar(不多于此,只是标准化的一种方式)。

我的问题是关于UTF-8,它是否使用了多个内存块?

假设我有一个变量:

  

unsigned char *string =“使用UTF8编码的字符串~> çã”;

如果我将变量声明为

  

unsigned

我只有127个值(因此我的程序将存储更多的内存块),还是UTF-8也会变成负数?

抱歉如果我不能正确解释,但我认为这有点复杂。

注意:感谢所有答案

我不明白它通常如何被解释。

我认为像ascii一样,如果我在程序中有一个有符号和无符号的字符,那么字符串将具有不同的值,这会导致混淆,在utf8中想象一下。


如果您使用的是现代计算机,无符号字符将支持0..255的值。但UTF-8是一种多字节编码 - 它为每个Unicode字符使用1..4个字节。 - Jonathan Leffler
我在谈论@Michael Burr所说的话。 - drigoSkalWalker
8个回答

7
我收到了几个要求解释我所说的评论。
当你比较字符并期望特定排序时,char类型可以默认为有符号或无符号类型,这一事实在某些情况下非常重要。特别是,UTF8使用高位(假设char是8位类型,在绝大多数平台上是真的)来表示一个字符代码点需要超过一个字节来表示。
以下是问题的一个快速而简单的示例:
#include <stdio.h>
int main( void)
{
    signed char flag = 0xf0;
    unsigned char uflag = 0xf0;

    if (flag < (signed char) 'z') {
        printf( "flag is smaller than 'z'\n");
    }
    else {
        printf( "flag is larger than 'z'\n");
    }    


    if (uflag < (unsigned char) 'z') {
        printf( "uflag is smaller than 'z'\n");
    }
    else {
        printf( "uflag is larger than 'z'\n");
    }
    return 0;
}

在我参与的大部分项目中,通常会避免使用未装饰的 char 类型,而是使用一个 typedef 明确指定一个 unsigned char。例如来自 stdint.huint8_t

typedef unsigned char u8;

通常处理无符号字符类型unsigned char似乎很顺利,问题不多——我偶尔遇到的一个问题是在使用该类型的内容控制循环时:

while (uchar_var-- >= 0) {
    // infinite loop...
}

6

有两点需要注意:

  1. Whether a char type is signed or unsigned won't affect your ability to translate UTF8-encoded-strings to and from whatever display string type you're using (WCHAR or whatnot). Don't worry about it, in other words: the UTF8 bytes are just bytes, and whatever you're using as an encoder/decoder will do the right thing.

  2. Some of your confusion may be that you're trying to do this:

    unsigned char *string = "This is a UTF8 string";
    

    Don't do this-- you're mixing different concepts. A UTF-8 encoded string is just a sequence of bytes. C string literals (as above) were not really designed to represent this; they're designed to represent "ASCII-encoded" strings. Although for some cases (like mine here) they end up being the same thing, in your example in the question, they may not. And certainly in other cases they won't be. Load your Unicode strings from an external resource. In general I'd be wary of embedding non-ASCII characters in a .c source file; even if the compiler knows what to do with them, other software in your toolchain may not.


5
使用unsigned char有其优点和缺点。最大的好处是您不会得到符号扩展或其他有趣的功能,例如有符号溢出,这些功能会产生意外的计算结果。unsigned char也与<cctype>宏/函数兼容,例如isalpha(ch)(所有这些都需要在unsigned char范围内的值)。另一方面,所有I/O函数都需要char*,因此在进行I/O时需要进行转换。
至于UTF-8,在有符号或无符号数组中存储它是可以的,但是您必须小心那些字符串文字,因为很少保证它们是有效的UTF-8。 C++0x添加了UTF-8字符串文字以避免可能的问题,我希望下一个C标准也会采用它们。
总的来说,只要确保源代码文件始终以UTF-8编码即可。

2

signed / unsigned 只影响算术运算。如果 char 是 unsigned,那么更高的值将是正数。对于 signed,则会是负数。但是范围仍然相同。


1

并不是这样的,unsigned / signed 并没有指定一个变量可以容纳多少个值。它们指定了这些值被如何解释

因此,unsigned charsigned char 有相同数量的值,除了前者没有负数而后者有。它仍然是8位(如果我们假设一个char包含8位,但我不确定在所有情况下都是这样)。


1
标准C保证字符至少拥有8个比特;现在很少有9位或10位字符的机器了。 - Jonathan Leffler

1

使用char*作为字符串时没有区别。唯一需要注意signed/unsigned的情况是当你需要将其解释为数字,例如进行算术运算或将其打印为整数时。


2
如果你正在比较字符,这也可能会产生影响。例如,在UTF8情况下,如果char是有符号的,'flag'字符通常会是负数。如果你的代码没有准备好处理这种情况,那么就会出现问题。 - Michael Burr
你能再解释一下吗? - drigoSkalWalker
@Michael Burr我不知道,你有参考资料吗? - Graphics Noob
1
关于负标志字符,这只有在您实际编写UTF8编/解码器时才会出现。如果那是一个黑盒子,那么一堆字节就是您所知道的输入/输出内容。 - Ben Zotto
@Graphics 和 drigoSkalWalker:我在这里扩展了我的评论:https://dev59.com/dnE85IYBdhLWcg3w9onw#2525010 - Michael Burr
Michael的观点是,你不能依赖于highValuedCharacter > lowValuedCharacter,因为有符号字符的高值会变成负数。一个显而易见的例子是,你不能通过检查> 127来检查UTF-8字符是否为非ASCII字符,因为在该范围内没有有符号字符。 - Chuck

0

关于你的问题:

如果我有一个带符号或无符号的字符数组,会导致我的程序运行错误吗?- drigoSkalWalker

是的。我的确遇到过这种情况。以下是我的应用程序中的简单可运行代码片段,如果使用普通的有符号字符,则完全出错。 在将所有参数中的字符更改为无符号后尝试运行它。像这样:

int is_valid(unsigned char c);

然后它应该正常工作。

#include <stdio.h>

int is_valid(char c);

int main() {

    char ch = 0xFE;
    int ans = is_valid(ch);
    printf("%d", ans);

}

int is_valid(char c) {
    if((c == 0xFF) || (c == 0xFE)) {
    printf("NOT valid\n");
        return 0;
    }
    else {
        printf("valid\n")
        return 1;
    }
}  

它的作用是验证字符是否为utf-8中的有效字节。 0xFF和0xFE不是utf-8中的有效字节。 如果函数验证它为有效字节,就会出现问题,请想象一下。
发生的情况是这样的:
0xFE
= 
11111110 
= 
254

如果您将此保存在普通字符中(即有符号字符),则最左边的位,即最高有效位,会使其变为负数。但这是什么负数呢?
它通过翻转位并添加一位来实现这一点。
11111110
00000001
00000001 + 00000001 =
00000010 = 2

记住它变成了负数,所以它变成了-2。

所以在函数中(-2 == 0xFE)当然是不正确的。 对于(-2 == 0xFF)也是一样。

因此,检查无效字节的函数最终会将无效字节验证为有效字节 :-o。

我可以想到坚持使用无符号字符处理utf-8的另外两个原因:

  1. 如果您可能需要向右进行一些位移,则可能会出现问题,因为如果使用有符号字符,则可能会从左侧添加1。

  2. utf-8和unicode仅使用正数,那么...为什么您不这样做呢?保持简单 :)


0

UTF-8 字符不能假定存储在一个字节中。UTF-8 字符可以是 1 到 4 个字节宽。因此,charwchar_tsignedunsigned 不足以假定一个单元始终可以存储一个 UTF-8 字符。

大多数平台(如 PHP、.NET 等)通常使用正常的字符串构建(例如 C 中的 char[]),并使用库在不同编码之间进行转换和解析字符串中的字符。


是的,很明显我需要一个字符数组,但我的问题是关于有符号和无符号的,如果我有一个有符号或无符号的字符数组,会导致程序运行错误吗? - drigoSkalWalker
其他答案都正确地指出,有符号/无符号并不会改变存储的数据大小。我的担忧只是UTF-8对于来自汉字、阿拉伯语等的UTF-8字符可能超过一个字节。 - spoulson

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