【问题标题】:How can I find the length of a character pointer using strlen() in C?如何在 C 中使用 strlen() 找到字符指针的长度?
【发布时间】:2019-06-03 10:23:23
【问题描述】:

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

但不幸的是,我有一个问题,任何答案都没有解决。 所以代码是:-

#include <string.h>

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

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

当我运行这段代码时,每次都会给出 0,正确答案是 0 和 127(我还是不明白为什么?)。

我的问题是这个语句是如何有效我的意思是我们正在计算 baz 的长度,其中包含一个内存地址,比如 0xb96eb740 这是一个十六进制数字,所以我们在做什么是strlen() 在这个地址上找到它的 长度吗?我的意思是我们如何才能找到一个地址的长度,这只是一个数字?

真的很困惑,并试图理解它几个小时,但仍然无法理解。

【问题讨论】:

    标签: c arrays pointers


    【解决方案1】:

    不要拘泥于它正在传递一个地址这一事实。 strlen()总是获取地址。它的参数是一个const char *,一个字符串的地址。所有这些调用都传递完全相同的地址:

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

    baz 被赋值为&amp;bar[0],所以第一个和第二个是等价的。 An array decays to a pointer to its first element (array == &amp;array[0]),所以第二个和第三个是等价的。

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

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

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

    正如链接的问答所解释的那样,该行为是不确定,因为bar[128] 数组的内容未初始化。该数组中可能有任何东西。我们知道值的唯一单元格是bar[127],它设置为\0。所有其他都未初始化。

    这意味着它们中的任何一个,或者全部,或者没有一个,可以包含\0 字符。它可以从一个运行到另一个运行,甚至从一个调用到另一个调用。每次您拨打foo() 时,您都会得到不同的结果。这是完全可能的。结果将根据调用 foo() 之前堆栈中的数据而有所不同。

    当我运行这段代码时,每次都会给出0,正确答案是0127(我还是不明白为什么?)。

    它可以返回 0 到 127 之间的任何值。由于不确定的行为,您不必过多了解程序在运行时发生返回的内容。如果您再次运行该程序,如果您在 foo() 之前调用一组不同的函数,如果您预先运行不同的程序,如果您更改编译器,如果您在一周中的不同日子运行它,如果您再次运行程序,输出可能会有所不同,如果你使用不同的操作系统等等等等等等。

    【讨论】:

    • 我知道这是真的。但是为什么strlen(baz) 返回 0?
    • @daya 为什么不呢?未初始化的内存也可能偶然在第一个位置包含 0
    • @daya 因为 baz[0] 恰好是 0
    • 没有“不确定的行为”这样的东西
    【解决方案2】:

    我的问题是这个语句是如何有效的,我的意思是我们正在计算 baz 的长度,其中包含一个内存地址,比如 0xb96eb740,它是一个十六进制数字,所以我们正在做的是这个地址上的 strlen() 来查找它的长度?

    strlen 函数接受地址作为参数,其行为是读取存储在该地址的字符。 (它不会像您建议的那样尝试读取地址 of 的字符)。如果那个字符不是'\0',那么它将读取下一个地址的字符,看看是不是'\0'等等。

    【讨论】:

    • linked answers 解释说它确实调用未定义的行为,只是不确定的行为。
    • @M.M 你有一些声称这是 UB 的来源吗?
    • @Broman 是的,委员会对 DR 451 的决议。但是在审查 OP 的问题时,我认为他只是专门询问strlen(ptr) 的工作原理,因此代码行为的主题可能仅限于另一个线程(因为在这里回答语言律师标准将淹没对 OP 特定问题的回答)
    • 我完全理解 strlen(baz) 是如何工作的,但仍然不确定未定义和不确定的事情。我认为这是不确定的行为,因为我们 无法 预测 what 将是这里的输出,但是 will 肯定是输出并且在另一方面是输出只是不确定的,就像我做2 / 0
    【解决方案3】:

    您的问题的答案是任何事情都可能发生

    数组bar 未初始化。只有bar[127] 明确设置为'\0'。将未初始化的数组传递给 strlen()(通过传递指向 bar[0]baz 间接完成)具有未定义的行为。

    在实践中,在没有陷阱值的现代架构上,函数 foo() 具有未指定的行为,并且可以返回 0127 之间的任何值,具体取决于您调用它时堆栈包含的内容。

    在您的情况下,它返回0,因为在bar 的开头碰巧有一个空字节,但是您不能依赖它,并且对foo() 的连续调用可能会返回不同的值。

    如果您在 valgrind 或其他内存清理工具下运行调用foo() 的程序,它可能会抱怨strlen() 访问未初始化的内存。

    【讨论】:

    • linked answers 解释说它确实调用未定义的行为,只是不确定的行为。
    • @JohnKugelman: barchar 的数组,与unsigned char 不同,我相信它可能有陷阱值,尽管它不能有填充位。现代系统没有我提到的陷阱值,但严格阅读标准允许此处未定义的行为,而不仅仅是不确定的行为。
    • 读取字符没有未定义的行为。 C 2018 6.2.6.1 5:“某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示形式,并且被 没有字符类型 的左值表达式读取,则行为未定义......”
    • @EricPostpischil:这句话并不意味着通过char 类型读取具有定义的行为,它仅适用于没有字符类型的左值表达式。 6.2.6.2 的第 3 段说 char 不能有填充位,但即使对于 char 类型,在非二进制补码架构上,负 0 也有可能成为陷阱表示。
    • 如果所讨论的对象未初始化并且从未按照 6.3.2.1p2 获取其地址,则即使读取字符类型也可能是 UB。
    【解决方案4】:

    其他人已经覆盖了该值是不确定,所以我直接进入这个:

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

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

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

    【讨论】:

    • @chqrlie 是的,我知道。我故意简化了它。在典型的实现中str 也应该是const char * 类型。这是我为了展示这个概念而删除的另一件事。
    • @chqrlie 你说得有道理。然而,它也不像是一个简化版本,因为算法是相同的并且具有相同的功能。 (长度 > INT_MAX 的字符串除外)
    • @chqrlie 因为它在某种程度上更复杂,所以这是错误的词。但我现在改写了整个句子。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-01
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 2022-01-04
    • 2013-12-09
    相关资源
    最近更新 更多