【问题标题】:C when should char** be null terminated?C char** 何时应为空终止?
【发布时间】:2016-02-11 05:56:10
【问题描述】:

在 StackOverflow 上找不到任何可以解决此问题的问题。

我知道 char* 数组不必以 NULL 结尾,但想知道您希望它何时结束?

例如,在调试我的代码时,我使用了大量的 printf() 来查看我的变量在代码的某个阶段是否正确。

我有一个包含 4 个 char* 的 char** 值,我将最后一个 char* 设为 NULL。 使用 NULL 终止,从 values[0] 到 values[3] 的 printfs 给了我这个 注意:names 只是我在完成打印值数组后立即打印的另一个数组

Testing values1[0]: %HOME/bin:%PATH
Testing values1[1]: /%HOME/include
Testing values1[2]: /%HOME/lib
Testing values1[3]: (null)
Testing names2[0]: PATH
Testing names2[1]: IDIR
Testing names2[2]: LIBDIR

我有一个带有 3 个 char* 的 char**,所有这些都是有效的 char*。 没有 NULL 终止,从 values[0] 到 values[3] 的 printf 给了我这个(名字不显示)

Testing values1[0]: %HOME/bin:%PATH
Testing values1[1]: /%HOME/include
Testing values1[2]: /%HOME/lib

我认为当 printf(...., values[3]) 是未定义的行为时,例如打印垃圾值,但如上面的输出所示,包括 printf(...., values[3]) 似乎没有被执行。

【问题讨论】:

  • NULLnull 不同...
  • 对不起,我没听懂。我做了 values[3] = NULL; NULL 和 null 有什么区别?
  • values[3] = NULL 是有效的,但是,printf("%s", values[3]) 不是...
  • 你的问题不是很清楚。打印值的循环是什么样的?将 NULL 指针作为指针数组的 sentlnel 值是有意义的,但您的程序也必须以某种方式使用该值。
  • 答案是:任何时候你将字符数组用作string(例如,将它传递给需要nul-terminated字符串的函数),数组必须是 nul-terminated 或未定义的行为将导致您传递数组的任何函数愉快地读取超出数组末尾以寻找 nul-terminating 字符。跨度>

标签: c arrays pointers char printf


【解决方案1】:

这里有很多混乱。首先:

  • NULL 指的是一个空指针常量,仅用于设置指向“无”的指针,即无效的内存位置。
  • Null 终止,有时拼写为“nul”以免与上述内容混淆,意味着在字符数组的末尾放置一个零字符 '\0'(有时称为“nul 字符”)以说明字符串的结束位置。它与 NULL 指针无关。比“空终止”更好的名称可能是零终止,因为这样更容易混淆。

现在,0、NULL 和'\0' 都给出了零值,因此它们实际上可能被用于错误的目的,而代码仍然可以工作。但是,您应该这样做:

  • 0 用于整数。
  • 使用NULL 作为指针。
  • 使用'\0' 终止字符数组,从而使其成为C 字符串。

接下来的困惑:

我有一个包含 4 个 char* 的 char** 值

指针不保存值。数组保存值。指针不是数组,数组不是指针。指针对指针不是数组,也不是二维数组。

虽然在某些情况下,您可以从数组中获取指向第一个元素的指针。

指向可变长度字符串的指针数组可以声明为:char* string_array [N];。您可以使用指向指针的指针来遍历该数组,但这不是一个好主意。更好的办法是使用数组索引:string_array[i]

总体而言,您实际上需要使用指针对指针的情况很少。通过函数参数返回指向已分配资源的指针是它们的正常用途。如果你发现自己在别处使用了指针对指针,这几乎是程序设计糟糕的某种迹象。

例如,一个非常广泛但 100% 错误使用指针对指针的特殊情况是在堆上动态分配二维数组时。


char** 什么时候应该为空终止?

从来没有。如上所述,这没有任何意义。您很可能不应该使用 char** 开始。

但是,您可以使用 NULL 结束字符指针数组,以指示数组的结尾。这是常见的做法,但不要将其与字符串的零终止混淆。

例子:

const char* str_array [] =
{
  "hello",
  "world",
  NULL
};

for(size_t i = 0; str_array[i] != NULL; i++)
{
  puts(str_array[i]);
}

【讨论】:

  • 感谢您的详细回复。它帮助我消除了很多误解。我只有一个关于 char*[] 和 char** 的问题。假设我有一个 char** 输出,我知道输出的大小,假设我有一个 for 循环输出,使用 i 作为索引。我知道 output[i] 相当于说 (output+1)。但是为什么即使输出是 char* 也能做到这个 output[i] = NULL 呢?我记得一些关于指针衰减成数组的事情(或者是相反的方式),但我不确定这是否与它有关
  • 顺便说一句,我的意思是(星号)(输出+1),但注释格式对我来说是陌生的,所以星号(引用)被解释为迷你 Markdown :(
  • @JJGong 是指针运算。如果您有一个char** output 指向字符指针数组的第一项,那么output[i] 将在该指针数组中为您提供指针编号i。正如int* output = int_array; ... output[i] 会给你一个整数数组中的整数i
  • 嗯,我明白了。我想我真正好奇的是在什么情况下你会使用一个字符指针数组而不是一个双字符指针。我觉得它们的功能几乎相同,除非双字符指针没有“大小”,除非您有另一个变量来跟踪大小,而字符指针数组在初始化时具有固定大小。但看起来你可以对这两种类型进行指针算术和索引引用。
【解决方案2】:

我从我的助教那里得到了答复,作为对“char** 何时应为空终止?”的回应。我认为这是合理的。如果您有其他理由这样做,那就太好了。

“这是一个很好的概念性问题,您可以将其视为类似于 C 字符串为何以空值结尾的原因。

假设您不想显式存储数组的长度(因为它是您管理和传递的额外数据等)。你怎么知道数组在哪里结束?最后的 NULL 充当标记值,因此您可以简单地对其进行迭代,直到达到神奇的数组结尾值。

如果您有一个固定的数组大小或以其他方式存储它,则 NULL 端不是必需的。"

【讨论】:

  • 同意这一点——你的 TA 的反应很好。我对此的看法是NULL 终止和维护计数都是确保您不会越界和调用未定义行为的有效方法。它们都有自己的位置,知道在特定情况下使用哪一个是优秀程序员的标志。
【解决方案3】:

每个字符串都需要以空值结尾。简单的选择是使用 null(即 0 或 '\0')来 memset 整个数组。

或者,如果您不想空终止,那么您需要跟踪字符串的长度。

【讨论】:

  • NULL'\0' 不同相同
  • 我知道字符串 (char*) 不需要以空值结尾。但是在什么情况下你会这样做,对于一个字符串数组(char** 或 char*[])。
【解决方案4】:

根据C11,第 7.21.6.1 章,fprintf()%s 转换说明符

s 如果不存在 l 长度修饰符,则参数应为指向初始值的指针 字符类型数组的元素。

因此,您不能将NULL 作为参数传递。它调用undefined behavior。你无法预测 UB 的行为。

您可以做的是,检查参数是否为!= NULL,然后传递变量。类似的东西

 if (values[n])
   puts(values[n]);

【讨论】:

    【解决方案5】:

    在 C99 (& POSIX) 中,char* 的唯一数组需要NULL 终止是 argv 的第二个参数 main。因此,您的 main 函数被(或应该)声明为

    int main(int argc, char**argv);
    

    并且(至少在 POSIX 系统上)它是必需的(并由运行时 crt0 强制执行),您应该期望:

    • argc 是肯定的
    • argv 是一个 argc+1 指针数组,每个指针都是一个 C 字符串(因此以 \0 字节结尾)
    • 它的最后一个元素argv[argc] 必须是NULL 指针
    • argv 中有两个不同的指针(即 argv[i]argv[j],两者都有 ij 为非负且小于argc+1) 不是pointer aliases(即相同的指针值)

    当然,某些库也可能具有其参数可能具有类似要求的函数。这应该记录在案。

    【讨论】:

      猜你喜欢
      • 2012-04-20
      • 2012-03-06
      • 1970-01-01
      • 2017-04-21
      • 2014-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-11
      相关资源
      最近更新 更多