-1

我是编程新手,几周前开始学习 c。我在一本书中读过字符数组应该以 结尾\0,但是当我创建一个没有 的数组时\0,它可以正常工作。这怎么可能。?

#include<stdio.h>
#include<string.h>
int main()
{
    char a[] = {'a','p','p','l','e'};

    printf("%d\n",strlen(a));
    printf("%s\n",a);
    return 0;
}

上面的代码输出是

5
apple

我还读到 char 是整数数据类型的子集,但是当我使用 int 数据类型创建上述数组时,它无法正常工作。

#include<stdio.h>
#include<string.h>
int main()
{
    int a[] = {'a','p','p','l','e'};

    printf("%d\n",strlen(a));
    printf("%s\n",a);
    return 0;
}

上面的代码输出是

1
a

为什么它只考虑数组的第一个元素?

4

6 回答 6

2

你问题的前半部分相当于这个:

我是新手,几周前开始了解道路交通。我看过一本书,说你要等绿灯才进入路口,但是当我不等待进入路口时,它就可以正常工作了。这怎么可能?

换句话说,你只是走运了。碰巧的是,即使您构造了一个没有适当\0终止符的字符数组,内存中恰好在ein之后有一个 0 字节apple,所以它仍然有效。但它根本不能保证会起作用,就像它不能保证你可以继续逆光过马路,最终不会被撞到一样。

继续你的第二个问题,当你读到“char是整数数据类型的一个子集”时,这根本不意味着你通常会使用 a 的任何地方char,你也可以使用int.

这是记忆中的一些字符。它们中的每一个都是一个字节的大小:

char c1 = 'p', c1 = 'e', c3 = 'a', c4 = 'r';

    +---+                   +---+
c1: | p |               c2: | e |
    +---+                   +---+

    +---+                   +---+
c3: | a |               c4: | r |
    +---+                   +---+

这是内存中的一些整数。在现代机器上,它们中的每一个都可能有四个字节大小:

int i1 = 'p', i1 = 'e', i3 = 'a', i4 = 'r';

    +---+---+---+---+       +---+---+---+---+
i1: | p             |   i2: | e             |
    +---+---+---+---+       +---+---+---+---+

    +---+---+---+---+       +---+---+---+---+
i3: | a             |   i4: | r             |
    +---+---+---+---+       +---+---+---+---+

这是一个正确以 null 结尾的 数组char

char ca[] = { 'p', 'e', 'a', 'r', '\0' };

    +---+---+---+---+---+
ca: | p | e | a | r |\0 |
    +---+---+---+---+---+

printf打印这个字符串或strlen计算它的长度时,它们从开头开始,一次一个字节地沿着字符串移动,直到找到\0.

但这里有一个数组int

int ia[] = { 'p', 'e', 'a', 'r', '\0' };

    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
ia: | p             | e             | a             | r             | \0            |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

但是我画的有点不对,因为实际上,每个 int 中额外的三个字节不是用空格填充的,而是用零字节填充的。(就好像我们想用前导零来表示数字1,也就是0001。)所以更准确的图是这样的;

    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
ia: | p  \0  \0  \0 | e  \0  \0  \0 | a  \0  \0  \0 | r  \0  \0  \0 | \0  \0  \0  \0|
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

因此,当printforstrlen从开头开始并一次一个字节地处理数组以寻找终止符\0时,他们会立即找到一个,就在第一个字母之后。

这里要考虑的重要一点是printfstrlen被定义为对char. 而且由于 C 的工作方式,他们无法知道您已作弊并传递了一个数组int。他们确实采用了相同的内存并将其视为 的数组char,因此得到的结果与您的预期截然不同。

因为很容易犯这样的错误,所以如果你犯了,好的编译器会警告你。对于您的代码,我的编译器给了我这些警告:

warning: incompatible pointer types passing 'int [5]' to parameter of type 'const char *'
warning: format specifies type 'char *' but the argument has type 'int *'

这些消息指的是 type char *,它是指向 - 的指针char,因为当您将数组传递给函数时,实际上传递的是指向数组第一个元素的指针。(但这是另一天的话题。但这与我所说的printfstrlen“从字面上获取相同的记忆并将其视为”它是一个字符数组有很大关系。)

于 2018-09-08T12:10:28.653 回答
2

char a[] = {'a','p','p','l','e'};中,编译器会计算您提供的值的数量,即 5。然后它创建一个由五个组成的数组char并用这些值初始化它们。

然后, inprintf("%d\n",strlen(a));和 in printf("%s\n",a);,行为不是由 C 标准定义的,因为您需要在数组中有一个零元素来指示结束的位置。在你尝试这个的情况下,可能发生了a数组后面的内存包含一个零,导致程序打印“5”和“apple”。然而,这不会总是发生。

此外,strlenhas type的结果size_t应该使用%zu而不是打印%d

int a[] = {'a','p','p','l','e'};中,编译器创建一个int. 当您在 中使用它时printf("%s\n",a);,您正在传递一个指向intwhenprintf需要指向的指针char。C 标准没有定义这种行为。一个常见的结果是,printf它将处理数组中的字节,int就好像它们是一个数组一样char,尽管这不能依赖——C 实现的实际行为可能会有所不同。

由于int比 宽charint包含该值的a一个通常包含一个带有该值的字节a和一个或多个带有零值的字节。它也可能包含填充位。intC 标准未定义an 中字节的顺序。如果包含的字节a恰好在内存中的第一个,并且后面的字节为零,printf则可能会打印“a”。但是,如果包含零的字节是第一个,printf则会将其视为字符串的结尾,并且不会打印任何内容。

同样,行为不是由 C 标准定义的。以上仅说明了您所看到的可能是如何被打印出来的,而不是您在其他情况下可以期待的。

于 2018-09-08T12:10:37.813 回答
1

传递int[]tostrlen()是错误的。strlen()期望字符。即使您提供其他内容(并关闭或忽略编译器的所有警报警告),也strlen()将给定地址解释为char*(无论它实际包含什么)。

严格来说,这是未定义的行为

稍微调查一下,我们可以探索可能发生的情况:

char a[] = {'a','p','p','l','e'};

定义一个 5 个字符的数组。从内存中转储,这可能看起来像这样:

0x61 0x70 0x70 0x6c 0x65 ???? ???? ????

int a[] = {'a','p','p','l','e'};,假设 32 位int,小端,这可能看起来像这样:

0x61 0x00 0x00 0x00 0x70 0x00 0x00 0x00
0x70 0x00 0x00 0x00 0x6c 0x00 0x00 0x00
0x65 0x00 0x00 0x00 ???? ???? ???? ????

重新解释a[]char*strlen()会做什么),这会产生一个长度为 1 的字符串。

但是,它仍然是未定义的行为......

于 2018-09-08T12:01:33.787 回答
1

取决于硬件和实现,int长度可以超过 2 个字节。

在 little endian 系统上,第一个字节将是 'a' 的 ASCII 码,第二个字节(以及直到 sizeof(int) 的连续字节)为零。因此任何字符串函数都会将其视为单个字符串。

大端系统将具有相反的字节顺序,如果我们将此 int arrar 解释为 char 数组,则第一个字符将为零,这将终止字符串,并且它的长度将为零。

您的第二个示例是错误的,因为您没有终止零并将其用作刺痛调用 UB。

你的 char 表初始化应该是:

char a[] = {'a','p','p','l','e', 0};

或者

char a[] = "apple";

因为字符串文字初始化也添加了终止的 nul 。

于 2018-09-08T12:42:14.043 回答
0

我在一本书中读过字符数组应该以\0...结尾

仅当您要将字符数组解释为字符串时才需要。在 C 语言中,字符串实际上是由空字符终止的一维字符数组\0

在您的第一个示例中,char数组a只是字符数组。你很幸运,strlen并且printf已经给出了预期的输出。该strlen函数返回终止空字符之前的字符数。在您的情况下,数组之后的内存a必须是0. 因此,您将获得预期的输出strlen。出于同样的原因,printf它也可以按预期工作,因为它将每个字节都写入并且不包括第一个空终止符。

在您的第二个示例中,您将一个整数指针传递给strlen

printf("%d\n",strlen(a));

编译器必须给出警告消息,因为参数类型strlenconst char *并且您正在传递它int *

此外,在printf您将参数作为整数指针给出。格式说明%s符需要一个char指针。在这种情况下,行为是未定义的。

于 2018-09-08T12:18:56.560 回答
0

在 32 位编译器上int占用 4 个字节并char占用 1 个字节。如果您将整数数组传递给strlen,它会从整数中扫描第一个字节,a在您的情况下,接下来的 3 个字节为 0,因此strlen在第二个字节处停止并显示length1

于 2018-09-08T12:08:07.627 回答