困惑于C语言字符串常量

5

当我看到这个Porters Stemming算法的C语言实现时,我发现了一个我不理解的C-ism。


(说明:本段内容讲述作者在查阅与Porters Stemming算法相关的C语言实现时,对其中某些C语言特有的写法感到困惑。)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void test( char *s )
{
    int len = s[0];

    printf("len= %i\n", len );

    printf("s[len] = %c\n", s[len] );
}

int main()
{
    test("\07" "abcdefg");

    return 0;
}

并输出:

len = 7
s[len] = g

然而,当我输入

test("\08" "abcdefgh");

或者任何长度超过7个字符的字符串常量与第一对括号中相应的长度(即 test("\09" "abcdefghi"); 的输出为

len = 0
s[len] = 

但是像 test("\01" "abcdefgh"); 这样的任何输入都会打印出该位置上的字符(如果我们暂时将第一个字符位置称为1而不是0)。
似乎 test(char *s) 读取了第一对括号中的数字(我不确定它是如何做到这一点的,因为我认为 s[0] 只能读取单个字符,即 '\'),并打印出第二对括号中字符串常量的该索引+1处的最后一个字符。
我的问题是:似乎我们正在将两个字符串常量传递给 test(char *s)。这里究竟发生了什么,也就是说,编译器似乎如何将字符串“分割”成两对括号?另一个可能会有的问题是,形如 "blah" "abcdefg" 的字符串是否是一段连续的内存块?可能我忽略了一些基本概念,但即使如此,我也想知道我忽略了什么。我知道这是一个基本概念,但我找不到一个清晰的例子或情境来解释这个问题,老实说我也不理解输出结果。欢迎任何有帮助的评论。

1
相邻的字符串字面量会被预处理器连接起来。 - Shafik Yaghmour
@ShafikYaghmour:是的,我认为这是有意的。看起来代码试图模拟Pascal字符串。 - Fred Larson
4个回答

8
这里至少涉及到以下三个方面:
  • 编译器将相邻的字符串字面量拼接在一起。例如,"a" "b""ab" 是一样的。

  • 反斜杠是转义字符,不会被直接复制到结果字符串中。符号 \01 表示“ASCII 值为 1 的字符”。

  • 符号 \0... 表示一个八进制字符常量。八进制数用 0 到 7 的数字组成。8 不是一个有效的八进制常量,所以 "\08" 不会跟在 "\07" 后面。


2
请注意,八进制的符号表示不一定是\0... - 例如,\33是一个完全有效的八进制字符常量。\08则被解释为\0后跟着8 - user4815162342
2
是的,在C语言中,可变长度字符串非常不寻常,你几乎找不到任何支持它们的地方。 - Greg Hewgill
1
长度字节和“实际字符串”被分开的原因可能需要一些额外的解释。 - Jongware

5
问题并不在于字符串的长度,而在于字符串字面值中用于指定不可打印值的\o语法。\o\oo\ooo表示八进制常量,即写成八进制形式的单个字符值。由于08\08中不代表一个有效的八进制数,因此它被解释为\0后面跟着ASCII字符8
要解决这个问题,请将8表示为\10\010
test("\007" "abcdefg");
test("\010" "abcdefgh");

...或者切换到十六进制,其中\x前缀使基数对于普通读者更加明确:

test("\x07" "abcdefg");
test("\x08" "abcdefgh");
test("\x09" "abcdefghi");
test("\x0a" "abcdefghij");
...

1

\number在字符或字符串字面值中表示其代码为number的字符。 number被解释为八进制,因此第一个非八进制数字终止该数字。 因此,"\07"是一个包含代码为7的字符的单字符字符串,但\08是一个包含代码为0的字符后跟数字8的双字符字符串。

此外,代码0是C中用于指示字符串结尾的空终止符。因此,第二个字符串在开头就结束了,因为它的第一个字节是终止符。这就是为什么您第二个示例中字符串的长度为0的原因。


1
当两个或多个字符串字面量相邻(仅由空格分隔)时,编译器将它们合并为单个字符串。因此,"\07" "abcdefg"等价于"\07abcdefg""\07"是一个八进制转义。八进制转义在三个数字后结束,或者在第一个非八进制字符处结束。因此,当您输入"\08"时,8是一个非八进制字符,因此转义结束,并在s[0]中存储0
现在,len0,打印s[len]将尝试打印s[0]处的字符,该字符具有不可打印的ASCII码(仅ASCII值大于32的字符可打印)。

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