在 C 中将空字符串文字初始化或分配给指向 char 的指针或在 C++ 中指向 const char 的指针的原因是什么?

What is the reason to initialize or assign an empty string literal to a pointer to char in C, or a pointer to const char in C++?

对于那些可能了解它的人来说,我的问题实际上很简单,但我问是因为我还不知道这种技术。

在 C 中将空字符串文字初始化或分配给指向 char 的指针或在 C++ 中指向 const char 的指针的原因是什么?

喜欢f.e.:

char* p = "";

char* p;
p = "";

或者,如评论中所建议,在 C++ 中:

const char* p = "";

const char* p;
p = "";

我确实经常看到这种技术,但不明白为什么有人要将空字符串文字分配给 char 指针(或 C++ 中指向 const char 的指针)或这是哪里来的来自.


我读过Initialize a string in C to empty string,但这个问题的重点是用空字符串初始化一个char数组。

我也读过:,但这个问题涵盖了将空字符串分配给先前由 malloc() 函数返回的指针的错误方法。

这完全取决于接下来的内容。例如,以下内容是可以接受的:

const char *p = "";

if (f()) {
   p = "Foo";
}
else if (g())
   p = "Bar";
}

strcat(msg, p);

也就是说,这不太可能发生。随后分配给 p 的值可能是指向需要释放的动态分配内存的指针,但 "" 无法释放,因此您最终会得到

char *p = "";
int free_it = 0;

if (f()) {
   p = ff();
   free_it = 1;
}
else if (g())
   p = gg();
   free_it = 1;
}

strcat(msg, p);
if (free_it)
   free(p);

当您可以轻松地

char *p = NULL;

if (f()) {
   p = ff();
}
else if (g())
   p = gg();
}

if (p)
   strcat(msg, p);

free(p);

中采用的方法导致无法释放值。拥有一个节点分配器和一个节点析构函数会更有意义。

int Node_init(Node *node, const char *value) {
    char *value_ = strdup(value);
    if (!value_)
       return 0;

    node->value = value_;
    node->sibling = NULL;
    node->child = NULL;
    return 1;
}

Node *Node_new(const char *value) {
   Node *node = malloc(sizeof(Node));
   if (!node) {
      return NULL;
   }

   if (!Node_init(node, value)) {
      free(node);
      return NULL;
   }

   return node;
}

void Node_destroy(Node *node) {
    free(node->value);
}

void Node_delete(Node *node) {
   Node_destroy(node);
   free(node);
}

int main(void) {
   Node root;
   Node_init(&root, "");
   ...
   Node_destroy(&root);
}

用空字符串文字初始化一个 char 指针确实有好处,实际上空字符串文字不是 "empty"。如果您创建一个虚拟程序并使用调试器查看 char* p = "";,您将看到创建了一个长度为 1 且包含 [=11=] 的字符数组。这意味着 p 指向有效的零终止字符串。因此,您可以将 p 传递给大多数使用零终止字符串的函数(例如,基本上所有标准库字符串操作函数),而不必担心它们 failing/memory 错误等。这很有用,例如在您正在为 p 分配一些值,这取决于某些可能会失败的条件,如果您没有使用适当的值初始化指针,就会给您留下潜在的未定义行为。

关于最后一点,还有一些编码标准禁止未初始化变量的问题,因为这些是错误的潜在来源。

我不一定会说这是某种“编码技术”。相反,我认为在很多情况下,它比其他选择更好。

初始化为 "" 有时可能是首选,因为它更安全。假设您有这样的代码:

const char* s;
if(some_condition) {
  s = something();
} else if(some_other_condition) {
  s = something_else();
}
for(const char* p = s; *p; ++p) {
  /* do something */
}

现在,假设您有 95% 的把握知道 some_conditionsome_other_condition 将始终为真,但这种代码对您来说仍然很可怕(对我来说也是如此) .

如果您根本没有初始化 s 并且 none 的条件为真,那么您的程序的行为是未定义的。它可能会崩溃,也可能不会。您以后将永远无法检查错误情况,因为 s 可以是任何字面意思。

如果您使用 NULL 初始化 s,您现在可以检查错误条件,但您的 for 循环仍然包含 UB。

这里最安全的方法显然是包含类似 else { assert(0); } 的内容并显式检查错误情况,但如果您的情况不需要这样做,您可以将 s 初始化为 "" 如果 some_conditionsome_other_condition 都为假,代码将什么都不做。

What is the reason to initialize or assign an empty string literal to a pointer to char in C or a pointer to const char in C++?

在我看来,这里似乎存在误区。指针 用空字符串初始化。它被初始化为 指向 一个空字符串(编译器放置在内存中某处的字符串文字)。这是一个主要的区别。

考虑这段代码:

char* p = "";
printf("%p\n", (void*)p);
p = "test";
printf("%p\n", (void*)p);

可能的输出:

0x563e72497007
0x563e72497008

在这些情况下,p 保存编译器放置两个字符串文字的内存地址(即“”在 0x563e72497007 和 "test" 在 0x563e72497008)。所以在那个记忆中你有:

0x563e72497007 '[=12=]'                  (i.e. the empty string that only consists 
                                           a string termination character`)

0x563e72497008 't' 'e' 's' 't' '[=12=]'  (i.e. the string "test")

所以再次 - p 不是 initialized/assigned 与字符串,它是 initialized/assigned 到 指向字符串 .

为什么要初始化一个指向空字符串的指针?

好吧,这就像您初始化的任何其他变量一样...您这样做是因为您希望该变量具有已知值,以防在对它进行任何其他赋值之前使用它。所以就像做 int i = 0;.

换句话说 - 你做 char* p = ""; 以确保 p 指向有效的 C 风格字符串。

一个非常简单的例子:

char* p = "";
if (answerIsWrong())
{
    p = "not";
}
printf("The answer is %s correct\n", p);

根据函数 answerIsWrong() 的 return 值,这可能会打印:

The answer is correct

The answer is not correct

在第一种情况下,重要的是 p 被初始化为 指向 一个空字符串。

但是,如果您 知道 在它被分配一个新值之前您永远不会使用 p,那么显然没有理由初始化它!但是,一些程序员更喜欢始终初始化所有变量 - 即使他们在使用前分配了另一个值。

示例:

char* p = "";  // No reason for this initialization
               // p will be assigned another value before it's used

if (answerIsWrong())
{
    p = " not ";
}
else
{
    p = " absolutely ";
}
printf("The answer is %s correct\n", p);
const char *p = "";

产生一个有效的以 null 结尾的字符串(代表一个空字符串)。因此它可以用于接受 c-style 字符串等的函数中

这不同于例如:

const char *p = nullptr;

这不是有效的字符串,并且在大多数接受 C 风格字符串的函数中都会失败(例如,std::string(nullptr) 会产生 UB,很可能会崩溃)。

我不会称之为编程技术。