在C语言中,如何最好地表示字符?

3
我知道一个char数据类型可以是带符号或无符号的,这取决于具体实现。如果我只是想操作字节,这并不会对我产生太大的困扰。(事实上,我认为char数据类型代表的是字节而不是字符)。
但是我了解到字符串字面值实际上是有符号的char数组(实际上它们不是,但请见下面的更新),函数fgetc()返回unsigned char被转换成int。所以如果我想操作字符,是使用有符号、无符号还是不确定的字符更好?为什么从文件读取字符有一种不同的约定,而字面值却没有?
我之所以问这个问题是因为我有一些用C语言编写的代码,用于比较字符串字面值和文件内容,但是使用带符号还是无符号char*可能会让我的代码出错。
更新1
好的,正如一些人在答案和评论中指出的那样,字符串字面值实际上是char数组,而不是signed char数组。这意味着我应该使用char*来表示字符串字面值,而不用考虑它们是带符号还是无符号的。这使我非常满意(直到我开始使用unsigned char进行转换/比较为止)。
然而,重要的问题仍然存在,如何从文件中读取字符并将其与字符串字面值进行比较。关键在于将���用fgetc()读取的int类型数据(它明确从文件中读取unsigned char)转换为允许带符号或无符号的char类型。让我提供一个更详细的例子。
int main(void)
{
    FILE *someFile = fopen("ThePathToSomeRealFile.html", "r");
    assert(someFile);

    char substringFromFile[25];
    memset((void*)substringFromFile,0,sizeof(substringFromFile));

    //Alright, the real example is to read the first few characters from the file
    //And then compare them to the string I expect
    const char *expectedString = "<!DOCTYPE";

    for( int counter = 0; counter < sizeof(expectedString)/sizeof(*expectedString); ++counter )
    {
        //Read it as an integer, because the function returns an `int`
        const int oneCharacter = fgetc(someFile);
        if( ferror(someFile) )
            return EXIT_FAILURE;
        if( int == EOF || feof(someFile) )
            break;

        assert(counter < sizeof(substringFromFile)/sizeof(*substringFromFile));

        //HERE IS THE PROBLEM:
        //I know the data contained in oneCharacter must be an unsigned char
        //Therefore, this is valid
        const unsigned char uChar = (const unsigned char)oneCharacter;
        //But then how do I assign it to the char?
        substringFromFile[counter] = (char)oneCharacter;
    }

    //and ultimately here's my goal
    int headerIsCorrect = strncmp(substringFromFile, expectedString, 9);

    if(headerIsCorrect != 0)
        return EXIT_SUCCESS;
    //else
    return EXIT_FAILURE;
}

基本上,我知道我的fgetc()函数返回的是一些经过错误检查后可以编码为无符号字符的内容。我知道char可能是无符号字符,也可能不是。这意味着,根据c标准的实现方式,将其转换为char将涉及没有重新解释的情况。然而,在系统实现为带符号char的情况下,我必须担心那些可以由无符号字符编码但不能由char编码的值(即那些在(INT8_MAX UINT8_MAX]之间的值)。
简而言之,
问题是这样的,我应该(1)复制他们通过fgetc()读取的底层数据(通过强制类型转换指针-别担心,我知道如何做),还是(2)从无符号字符向下转换为char(只有当我知道这些值不会超过INT8_MAX或这些值可以被忽略的情况下才安全)?

不是因为是否允许char是有符号还是无符号,而是标准未指定普通char是有符号还是无符号。如果您开始使用unsigned char*来表示字符串,那么就会像您已经发现的那样,破坏所有对字符串库函数的调用。虽然可以通过强制转换来修补,但真正的解决方案是一开始就不要这样做。 - Michi
2个回答

1
历史原因是(据我所知,没有参考资料),从一开始,char类型的规范就很差。一些实现使用“一致的整数类型”,其中char、short、int等默认都是带符号的。这是有意义的,因为它使类型彼此一致。其他实现使用无符号字符,因为从未存在过负索引的符号表(那将是愚蠢的),并且因为他们看到了需要超过128个字符的情况(一个非常合理的担忧)。当C得到适当的标准化时,要改变这种情况已经太晚了,市场上已经有太多不同的编译器和为它们编写的程序。因此,char的符号被定义为实现定义,出于向后兼容性的原因。如果您只使用char存储字符/字符串,则其符号并不重要。仅当您决定在算术表达式中涉及char类型或将其用于存储整数值时,它才会有影响-这是一个非常糟糕的想法。
  • 对于字符/字符串,始终使用char(或)。
  • 对于任何其他形式的1字节大数据,始终使用uint8_tint8_t

但是,如果我理解正确,字符串字面量是有符号字符。
不是的,字符串字面量是char数组。
函数fgetc()返回无符号字符转换为int。
不是的,它返回一个char转换为int。它是int类型,因为返回类型可能包含EOF,这是一个整数常量而不是字符常量。
有一个signed char*和unsigned char*之间的区别可能会使我的代码容易出错。
不完全是这样。从标准中正式适用以下规则:
对象类型的指针可以被转换为不同对象类型的指针。如果结果指针未正确对齐以引用类型,则行为是未定义的。否则,当再次转换时,结果应与原始指针相等。
不存在任何情况下,从指向signed char到指向unsigned char或反之亦然的指针转换会导致任何对齐问题或其他问题。

不,它返回一个转换为int的char。这是否意味着符号扩展,从而会破坏程序?(小型反例。) - xaizek
根据POSIX用户手册(http://www.unix.com/man-page/POSIX/3posix/fgetc/),“如果流指针所指向的输入流没有设置文件结束标志并且下一个字节存在,则fgetc()函数将获取下一个字节作为无符号字符转换为int”。这意味着它不返回`char`,而是返回`unsigned char`,因此我有疑问。 - xaviersjs
问题实际上不是如何转换指针并使用它,而是当我的基础字符为有符号(或潜在无符号)时,将其转换为unsigned char *以从文件中读取数据是否会导致数据的错误解释。如果我想将文件中的字符与字符串文字中的字符进行比较,当我从fgetc读入到我的char *数组时,我应该转换指针还是值? - xaviersjs
@xaviersjs 哦,是的,你说得对。你引用的文本实际上是直接从C标准中取出的。但是,从(可能带符号的)char转换为unsigned char是完全安全的。最好将值强制转换而不是指针,但在这种特定情况下,这并不重要。 - Lundin
@Lundin,转换的方向相反,从无符号字符(编码在“int”中)到“char”(有符号/无符号,我们不知道)。我更新了问题,试图更好地解决这个问题,但是有些值只能由一个(有符号/无符号字符)进行独占编码。 - xaviersjs

1
我知道 char 根据实现可以是有符号或无符号的。如果我只想操作字节,这并不会真正困扰我。
但是,如果您要比较或将 char 赋值给其他整数类型,则应该关注它。
但是,如果我理解正确,字符串文字是有符号 char。
它们是类型为 char[] 的,因此如果 char === unsigned char,则所有字符串文字都是 unsigned char[]。
函数 fgetc() 返回转换为 int 的无符号 char,这是正确的,并且需要省略不需要的符号扩展。
因此,如果我想操作字符,使用有符号、无符号还是模糊的字符更好呢?
为了可移植性,我建议遵循各种libc实现采用的做法:使用char,但在处理之前将其转换为unsigned char(从char*unsigned char*)。这样,隐式整数提升不会将范围在0x80 - 0xff之间的字符转换为更宽类型的负数。
简而言之:(signed char)a < (signed char)b并不总是等同于(unsigned char)a < (unsigned char)b。这里有一个example

为什么从文件中读取字符与字面量有不同的约定?

getc()需要一种返回EOF的方法,以便它不能与任何真实的char混淆。

如果你要比较将字符赋值给其他整数类型,那么这应该会让你感到困扰。我确实感到困扰,这就是为什么我在问这个问题。fgetc()返回一个int,因此我需要知道如何转换。我要么必须在某个地方指定一个unsigned char *并将其强制转换为unsigned char,要么我可以通过将其强制转换为char来潜在地截断(这取决于编译器是否截断)。 - xaviersjs
@xaviersjs,在这种情况下,向下转换是安全的。如果 fgetc() 返回 EOF,则不会将其存储在 char 中,否则使用 signed charcharunsigned char 都是完全可以的,因为截断不涉及除了丢失高位(在这种情况下都是零)之外的其他隐式操作,所以没有损失。 - xaizek

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