在C语言中如何使用strlen()函数来查找字符指针的长度?

3

我明确表示我的问题与这个问题完全相同。

但不幸的是,我有一个问题,任何答案都没有解决。

所以代码是:

#include <string.h>

int foo(void) {
  char bar[128];
  char *baz = &bar[0];
  baz[127] = 0;
  return strlen(baz);
}

问题是:该函数的可能输出是什么?

每次我运行此代码时,它都会给出0,正确答案是0和127(我仍然不知道为什么?)。

我的问题是,这个语句怎么可能是有效的,我的意思是我们正在计算包含一个内存地址,比如0xb96eb740baz的长度,这是一个十六进制数字,那么我们所做的就是对这个地址进行strlen()以找到它的长度吗?我的意思是,我们怎么能找到一个地址的长度,它只是一个数字?

我很困惑,已经花了好几个小时尝试理解它,但还是没有搞明白。

4个回答

9

不要纠结于它被传递了一个地址。 strlen() 总是需要一个地址。它的参数是一个 const char *,即字符串的地址。所有这些调用都传递了完全相同的地址:

strlen(baz);
strlen(&bar[0]);
strlen(bar);

baz被赋值为&bar[0],因此第一和第二个是等价的。数组衰减为指向其第一个元素的指针array == &array[0]),所以第二和第三个是等价的。

我的意思是,我们如何找到一个地址的长度,它只是一个数字?

假设根据您的示例,bar == &bar[0] == baz == (char *) 0xb96eb740strlen()首先检查内存位置0xb96eb740是否包含\0。如果没有,则它将检查0xb96eb741。然后是0xb96eb742。然后是0xb96eb743。它将继续按顺序检查每个位置,直到找到\0

我知道这是真的。但为什么strlen(baz)返回0?

正如相关问题和答案所解释的那样,这种行为是不确定的,因为`bar[128]`数组的内容未初始化。该数组中可以包含任何内容,我们只知道一个单元格的值,即`bar[127]`,它设置为`\0`。所有其他单元都未初始化。
这意味着它们中的任何一个,或全部,或没有一个,都可能包含`\0`字符。它甚至可能在每次调用时运行到运行发生改变。每次调用`foo()`时,结果都可能不同。这完全有可能。结果将根据在调用`foo()`之前在堆栈上发生的数据而变化。
当我运行此代码时,每次给出的结果都是0,而正确的答案是0和127(我仍然不明白为什么?)。

它可以返回0和127之间的任何值。由于不确定的行为,您不应该过多地解读程序在运行时返回的内容。如果您再次运行程序,调用foo()之前调用了不同的函数集,或者在运行之前运行了不同的程序,更改编译器,或者在一周的不同日期运行它,使用不同的操作系统等等,输出可能会有所不同。


我知道这是真的。但为什么 strlen(baz) 返回 0? - daya
@daya 为什么不呢?未初始化的内存也可能偶然地在第一个位置包含一个 0 - Gerhardh
@daya 因为baz [0]恰好是0。 - klutt
3
“indeterminate behaviour”这个概念不存在。 - M.M

2
我的问题是这个语句怎么合法,我的意思是我们计算的是baz的长度,它包含一个内存地址,比如说0xb96eb740,这是一个十六进制数,那么我们现在所做的就是对这个地址使用strlen()函数来计算它的长度吗?
strlen()函数接受一个地址作为参数,并且它的行为是读取存储在该地址中的字符。(它不会像你所认为的那样去读取地址中的字符)。如果该字符不是'\0',那么它将读取下一个地址处的字符,看看是否是'\0'等等。

1
链接的答案解释说,它并没有引起未定义行为,只是不确定的行为。 - John Kugelman
@M.M 你有关于这是未定义行为的声明的一些来源吗? - klutt
2
@Broman 是的,委员会通过了 DR 451 的决议。但是在审查 OP 的问题后,我认为他只是在询问 strlen(ptr) 如何工作,因此代码行为的主题可以限制在其他线程中(因为在这里回答它以语言律师的标准将淹没对 OP 具体问题的回答)。 - M.M
我完全理解了strlen(baz)的工作原理,但仍然不确定未定义和不确定的事情。我的想法是这是不确定的行为,因为我们无法预测这里的输出会是什么,但肯定会有一个输出;而另一方面,未定义是指输出只是未定义的,就像如果我执行2 / 0 - daya

2
你的问题的答案是“任何事情都可能发生”。
数组bar未初始化,只有bar[127]明确设置为'\0'。通过间接传递指向bar[0]baz来传递未初始化的数组到strlen()中,这会导致未定义的行为。
在实践中,在现代架构中没有陷阱值的情况下,函数foo()具有未指定的行为,并且可以在调用时返回0127之间的任何值,具体取决于堆栈包含的内容。
在你的情况下,它返回0,因为刚好在bar的开头有一个空字节,但你不能依赖此结果并且连续调用foo()可能会返回不同的值。
如果运行一个调用foo()的程序,在valgrind或其他内存检测工具下,可能会抱怨strlen()访问未初始化的内存。

@JohnKugelman:bar是一个char数组,与unsigned char不同,它可能具有陷阱值,尽管它不能具有填充位。现代系统没有像我之前提到的那样的陷阱值,但标准的严格解释允许在这里出现未定义行为,而不仅仅是不确定行为。 - chqrlie
读取字符不会产生未定义的行为。C 2018 6.2.6.1 5:“某些对象表示不需要表示对象类型的值。如果对象的存储值具有这种表示,并且被lvalue表达式读取,该表达式不具有字符类型,则行为是未定义的...” - Eric Postpischil
即使是读取字符类型,如果所涉及的对象未初始化并且从未获取其地址,也可能会产生未定义行为,根据6.3.2.1p2。 - dbush
@chqrlie:标准中没有其他内容说明读取字符值可能会陷入困境。这就是启用陷阱的段落。 - Eric Postpischil
@dbush:数组元素自动符合“已取其地址”;它们都是通过计算其地址来访问的。从技术上讲,C标准中的要求是“可以用register存储类声明”。您可以声明一个register数组,但是它的唯一定义行为是在sizeof中使用它。在关于未初始化对象的规则6.3.2.1 2中使用的意义上,否,数组不能被声明为register。如果您使用指针或数组来访问对象,则不适用6.3.2.1 2,因为它的地址已被获取。 - Eric Postpischil
显示剩余2条评论

1

其他人已经提到了该值是不确定的,所以我直接进入正题:

我的意思是,我们如何找到一个地址的长度,它只是一个数字?

你不需要这样做。字符串的长度是通过从你想要开始的地址顺序读取内存,并查看在第一个'\0'字符之前需要走多远来计算的。以下是如何实现返回字符串长度的函数的示例:

int strlen(char * str) {
    int length=0;
    while(str[length] != '\0') 
        length++;
    return length;
}

@chqrlie 是的,我知道。我故意简化了它。在典型的实现中,str 也应该是 const char * 类型。这是我为了展示概念而删除的另一件事情。 - klutt
@chqrlie 你说得有道理。然而,它也不像是一个简化版本,因为算法是相同的,并且具有相同的功能。(除了长度> INT_MAX的字符串) - klutt
@chqrlie 因为它在某种程度上更加复杂,所以“wrong”这个词不太合适。但是我已经重新表述了整个句子。 - klutt

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