此源代码在C中打开字符串。它是如何做到的?


106

我正在阅读一些仿真器代码,并且已经发现了一些奇怪的东西:

switch (reg){
    case 'eax':
    /* and so on*/
}

这怎么可能?我以为您只能switch使用整数类型。是否有一些宏观骗局?


29
它不是字符串 'eax',它枚举常量整数值
P__J__

12
单引号,而不是双引号。字符常量被提升为int,因此是合法的。但是,多字符常量的值是实现定义的,因此该代码可能无法在其他编译器上正常工作。例如,eax可能是0x650x6561780x656178000x7861650x6165,或别的东西。
戴维斯洛

2
@Davislor:给定变量“ reg”的名称,以及eax是x86寄存器的事实,我猜想实现定义的行为是可以的,因为它在代码中使用的所有地方都是相同的。'eax' != 'ebx'当然,只要,它就只会使您的一个或两个示例失败。尽管可能在某些地方实际上采用了某些代码*(int*)("eax") == 'eax',因此大多数示例都失败了。
史蒂夫·杰索普

2
@SteveJessop我不同意您说的话,但是确实存在危险,有人可能试图在不同的编译器上编译代码,即使对于相同的体系结构,也会得到不同的行为。例如,'eax'可能比较等于'ebx'或等于'ax',而switch语句将无法按预期工作。
戴维斯洛

1
如果您查找/向我们显示reg的数据类型,那么所有这些奥秘将很快被消除。
部份

Answers:


146

(只有您可以回答“宏骗术”部分-除非粘贴更多的代码。但是这里没有太多内容可以使宏起作用-正式地,您不能重新定义关键字;这样做的行为是不确定的。)

为了实现程序的可读性,机智的开发人员正在利用实现定义的行为'eax'不是一个字符串,而是一个多字符常量。请非常小心地注意周围的单引号字符eaxint在您的情况下,最有可能给您的是该字符组合所独有的。(通常每个字符在32位中占据8位int)。每个人都知道,你可以switchint

最后,一个标准参考:

C99标准说:

6.4.4.4p10:“包含多个字符(例如,'ab')或不映射到单字节执行字符的字符或转义序列的整数字符常量的值是实现定义的。 ”


55
以防万一有人看到并感到恐慌,需要“实现定义”才能正常工作,并由编译器以某种适当的方式对其进行记录(该标准并不要求其行为直观或文档良好,但...)。对于完全了解自己编写内容的编码人员而言,这是“安全的”,而不是“未定义的”。
Leushenko '17

7
@Justin虽然可以,但这将是相当不正常的。如果它不执行该操作,那么答案很可能是,下一个可能性可能是它仅使用第一个字符而忽略其余字符。
Barmar

5
@ZanLynx我不是很肯定,但是我相信该功能早于Unicode和其他MBCS标准。内存转储和RIFF样式的文件格式块ID中看起来像文本的“魔术数字”是我所知道的第一个应用程序。
罗素·博罗戈夫

16
@ jpmc26这不是未定义的行为,它是实现定义的。因此,除非编译器文档提到恶魔,否则您的鼻子是安全的。
巴玛(Barmar)'17年

7
@ZanLynx:恐怕原始意图比Unicode,UTF-8和任何多字节字符编码要早20年。多字符常量只是表达表示2、3或4个字节组的整数(取决于字节和int大小)的一种简便方法。由于实现和体系结构之间的不一致,委员会将该声明声明为实现定义,这意味着没有可移植的方法来计算'ab'from 'a'和的值'b'
chqrlie

45

根据C标准(6.8.4.2 switch语句)

3 每个case标签的表达式应为整数常量表达式 ...

和(6.6常量表达式)

6 整数常量表达式应具有整数类型,并且仅应具有整数常量,枚举常量, 字符常量,结果为整数常量的sizeof表达式以及作为强制类型转换的立即数的浮点常量的操作数。整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型,除非作为操作数的一部分作为sizeof运算符。

现在是什么'eax'

C标准(6.4.4.4字符常量)

2整数字符常量是由单引号引起的一个或多个多字节字符的序列,例如'x'...

因此,'eax'根据同一部分的第10段,整数字符常量为

  1. ...包含多个字符(例如'ab')或不映射到单字节执行字符的字符或转义序列的整数字符常量的值是实现定义的。

因此,根据第一个提到的引号,它可以是整数常量表达式的操作数,可以用作大小写标签。

请注意,字符常量(用单引号引起来)具有类型,int并且与具有字符数组类型的字符串文字(用双引号引起来的字符序列)不同。


12

正如其他人所说,这是一个int常数,其实际值是实现定义的。

我假设其余代码看起来像

if (SOMETHING)
    reg='eax';
...
switch (reg){
    case 'eax':
    /* and so on*/
}

您可以确定第一部分中的“ eax”具有与第二部分中的“ eax”相同的值,所以一切都可以,对吗?...错了。

@Davislor在评论中列出了“ eax”的一些可能值:

... 0x650x6561780x656178000x7861650x6165,还是其他什么东西

注意第一个电位值?就是'e',忽略了其他两个字符。问题是程序可能使用'eax''ebx'等等。如果所有这些常量的'e'最终值都相同

switch (reg){
    case 'e':
       ...
    case 'e':
       ...
    ...
}

这看起来不太好,是吗?

关于“实现定义的”的好处是,程序员可以检查其编译器的文档,并查看其是否对这些常量有意义。如果可以,请免费回家。

不好的地方是,其他一些可怜的家伙可以接受代码并尝试使用其他一些编译器对其进行编译。即时编译错误。该程序不可移植。

正如@zwol在评论中指出的那样,情况并没有我想的那么糟糕,在糟糕的情况下,代码无法编译。这至少会为您提供该问题的确切文件名和行号。不过,您将没有有效的程序。


1
除了某种形式之外, assert('eax' != 'ebx'); //if this fails you can't compile the code because...原始作者可以采取任何措施来防止其他编译器失败而又不完全替换该结构>
Dan是由Firelight摆弄的东西Firelight

6
两个具有相同值的case标签违反约束条件(6.8.4.2p3:“ ...转换后,同一switch语句中的两个case常量表达式均不得具有相同的值”),只要所有代码将这些常量的值视为不透明,这样可以保证工作或编译失败。
zwol

更糟糕的是,在另一个编译器上进行编译的可怜的家伙可能不会看到任何编译时错误(打开int很好)。相反,运行时错误将会出现……
tucuxi

1

代码片段使用称为多字符字符常数的历史奇数,也称为多字符

'eax' 是一个整数常量,其值由实现定义。

这是有关多字符及其使用方法的有趣页面,但不应:

http://www.zipcon.net/~swhite/docs/computers/languages/c_multi-char_const.html


再往后看,这是丹尼斯·里奇(Dennis Ritchie)最初的C手册如何追溯过去的好时光(https://www.bell-labs.com/usr/dmr/www/cman.pdf)如何指定字符常量。

2.3.2字符常量

字符常量是用单引号''括起来的1或2个字符'。在字符常量中,单引号前必须带有反斜杠\“”。某些非图形字符和“” \本身可以根据下表进行转义:

    BS \b
    NL \n
    CR \r
    HT \t
    ddd \ddd
    \ \\

转义\ddd符“”由反斜杠组成,后跟1、2或3个八进制数字,用于指定所需字符的值。这种结构的特殊情况是“” \0(不跟数字),表示空字符。

字符常量的行为完全类似于整数(特别是不像字符类型的对象)。与PDP-11的寻址结构一致,长度为1的字符常量在低位字节中具有给定字符的代码,在高位字节中具有0的字符;长度为2的字符常量在低字节中具有第一个字符的代码,在高位字节中具有第二个字符的代码。具有多个字符的字符常量本质上与机器有关,应避免使用。

最后一句话是您需要记住的关于这种奇怪结构的所有信息:具有多个字符的字符常量本质上与机器相关,应避免使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.