在C中表示字符的最佳方式是什么?

时间:2015-08-14 11:20:42

标签: c char character c99

我知道根据实施情况,允许char签名或签名。如果我想做的就是操纵字节,这并不会让我感到烦恼。 (事实上​​,我不认为char数据类型是一个字符,而是一个字节。)

但是,如果我理解,字符串文字是signed char s(实际上它们不是,但请参阅下面的更新),函数fgetc()将unsigned char返回到{{1} }}。因此,如果我想操纵字符,使用有符号,无符号或模糊字符的首选样式是什么?为什么从文件中读取字符的惯例与文字不同?

我问,因为我在c中有一些代码在字符串文字和文件内容之间进行字符串比较,但是int vs signed char *可能会让我的代码容易出错。

更新1

好的,有些人指出(在答案和评论中)字符串文字实际上是unsigned char *数组,而不是char数组。这意味着我真的应该使用signed char来表示字符串文字,而不是考虑它们是有符号还是无符号。这让我非常高兴(直到我必须开始使用无符号字符进行转换/比较)。

然而,重要的问题仍然是,如何从文件中读取字符,并将它们与字符串文字进行比较。其中的关键是使用fgetc()从char *读取转换为明确从文件中读取intunsigned char类型,允许签名或无符号的。

请允许我提供更详细的例子。

char

基本上,我知道我的fgetc()函数返回的东西(经过一些错误检查后)可编码为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; } 。我知道unsigned char可能是也可能不是char。这意味着,根据c标准的实现,对unsigned char执行转换将涉及 no 重新解释。但是,如果系统是使用签名char实现的,我不得不担心char无法编码的unsigned char可以编码的值(即(INT8_MAX UINT8_MAX])之间的那些值。

TL;博士

问题是,如果我(1)复制他们的fgetc()读取的基础数据(通过铸造指针 - 不要担心,我知道该怎么做),或者(2)从{{1到char(如果我知道值不能超过INT8_MAX,那么这是安全的,或者无论出于何种原因可以忽略这些值)?

2 个答案:

答案 0 :(得分:1)

历史原因是(正如我所知,我没有参考),char类型从一开始就没有明确规定。

某些实现使用了“一致的整数类型”,其中charshortint等都是默认签名的。这是有道理的,因为它使类型彼此一致。

其他实现使用unsigned作为字符,因为从来没有任何带有负索引的符号表(这将是愚蠢的),因为他们看到需要超过128个字符(一个非常有效的问题)。

当C正确标准化时,改变它已经太晚了,为它们编写的太多不同的编译器和程序已经在市场上销售。因此,出于向后兼容性原因,char的签名已由实现定义。

char的签名无关紧要,如果您只使用它来存储字符/字符串。只有当您决定将char类型包含在算术表达式中或使用它来存储整数值时,这才是最重要的 - 这是一个非常糟糕的主意。

  • 对于字符/字符串,请始终使用char(或wchar_t)。
  • 对于任何其他形式的1字节大数据,请始终使用uint8_tint8_t
  

但是,如果我理解,字符串文字是签名字符

不,字符串文字是char数组。

  

函数fgetc()返回转换为int

的无符号字符

不,它返回char转换为int。它是int,因为返回类型可能包含EOF,它是一个整数常量而不是字符常量。

  

使用signed char * vs unsigned char *可能会使我的代码容易出错。

不,不是真的。形式上,该标准的规则适用:

  

指向对象类型的指针可以转换为指向不同对象类型的指针。如果   结果指针未正确对齐引用类型,行为未定义。否则,当再次转换回来时,结果将与原始指针进行比较。

不存在从指向signed char到指向unsigned char的指针,反之亦然,会导致任何对齐问题或其他问题。

答案 1 :(得分:1)

  

我知道根据实现情况允许签名或取消签名。如果我想要做的就是操纵字节,这并不会让我感到烦恼。

如果您要进行比较或将char指定给其他整数类型,那么它应该让您感到烦恼。

  

但是,如果我理解,字符串文字是签名字符

它们的类型为char[],因此,如果char === unsigned char,则所有字符串文字都为unsigned char[]

  

函数fgetc()返回已转换为int的无符号字符。

这是正确的,并且需要省略不需要的符号扩展名。

  

因此,如果我想操纵字符,使用有符号,无符号或不明确的字符是首选样式吗?

为了便携性,我建议遵循各种libc实现所采用的做法:使用char,但在处理强制转换为unsigned charchar*unsigned char*之前)。这种方式隐式整数提升不会将0x80 - 0xff范围内的字符转换为较宽类型的负数。

简而言之:(signed char)a < (signed char)b并不总是等同于(unsigned char)a < (unsigned char)b。这是一个example

  

为什么从文件中读取字符的惯例与文字不同?

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