假设你从 ,isupper() 中获取了一个看起来很无辜的函数。
它被定义为int isupper(int c);。所以它需要一个 int 并返回一个 int。
现在,假设你不是一个非常细心的程序员,你只是将你的 char 传递给这个函数。你自己想:“会出什么问题?这是我所知道的最简单的函数!”。
但你错了。在某个地方,有人会让她的 MP3 播放器因为这个可怕的错误而陷入无休止的崩溃循环。
这就是原因。 C 中最烦人的类型是 char。它可以是有符号的,也可以是无符号的,你可以以一种或另一种方式强制编译器(但随后你会打开另一个蠕虫罐),最糟糕的是,标准 C 库到处都使用这种类型!
所以,您使用 char,但您不知道它实际上已在您的环境中签名。你把它当作世界是一个 ASCII 世界来使用。
但世界并非如此。那个快乐的 MP3 拥有者现在正在听一首著名的德国歌曲,其名称中包含字母 ä(“扩展 ASCII 码 132”)。
你将这个字符传递给isupper(),编译器会做以下可怕的事情:
“啊,它是一个字符,但是函数需要一个整数。我知道!我不会警告程序员,因为那太简单了。我只是将字符转换为整数并传递它。我该怎么做?让我们检查一下 C 标准……嗯……很简单,只需取值并对其进行符号扩展(因为 char 是有符号的,你不知道吗?)。现在,这个字符的值是 -124,所以我只需将其转换为值为 -124 的 int。这很简单,我看不出有什么大惊小怪的。我为什么还要警告程序员?!”
现在isupper() 使用 -124 而不是 132 调用。
但这有什么问题呢?什么都没有,除了编译器附带的 C 库使用一个简单的 128 字节数组实现 isupper():它只是返回给定索引处的值。数组初始化为 0,除了大写的 ASCII 代码,它是 1。这样一个简单而优雅的实现......
但是等等,如果你将一个负值传递给这个函数会发生什么?好吧,那是不允许的:
c 参数是一个 int,应用程序应将其值
确保是可表示为无符号字符或等于的字符
宏 EOF 的值。如果参数有任何其他值,则
行为未定义。
所以,未定义的行为。在这种情况下,它会尝试访问不属于该进程的内存,然后 BAM!程序崩溃了。
所以你看,char 是邪恶的,你不应该使用它,除非你真的了解如何正确使用它。
(*) 正如 Keith Thompson 在评论中所说,当然不可能避免使用char。从strlen() 到curl_easy_escape(),大家都用char。但是您应该注意到int 的转换,尤其是当char 可能包含负数时。 函数和数组索引是容易犯代价高昂错误的两个地方。