使用指针作为参数的函数如何返回指针?

6

我正在阅读这本书:"C从A到Z"

有一个例子。

/* ptr14.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Fehler: Funktion gibt die Adresse
 * einer lokalen Variablen zurück. */
/* [ Error: Function returns the address of a
     a local variable. ] */
// ...

/* Möglichkeit2: Speicher vom Heap verwenden */
/* [ Option2: Use memory from the heap ] */
char *test3(void){
   char *buffer = (char *) malloc(10);
   strcpy(buffer, "testwert");
   return buffer;
}

/* Möglichkeit3: Einen Zeiger als Argument übergeben */
/* [ Option3: Pass a pointer as argument ] */
char *test4(char *ptr){
   char buffer[10];
   ptr = buffer;
   strcpy(buffer, "testwert");
   return ptr;
}
int main(void) {
   char *ptr;

   /* ... */

   ptr = test3();
   printf("test3: %s\n", ptr);
   test4(ptr);
   printf("test4: %s\n", ptr);
   return EXIT_SUCCESS;
}

我理解作者所讨论的问题。

为什么test4解决方案有效?

如果我理解正确,它不是在:

  1. 栈上分配了char buffer[10];
  2. buffer第一个元素的地址赋给前一作用域中的ptr (即ptr = buffer;)

我的期望:

使用ptr指向buffer应该会失败,因为该作用域应该被清除/清理掉。

我的想法有什么问题吗?

编辑 1

我将test4(ptr);更改为ptr = test4(ptr),它仍然有效...

仍然不知道为什么test4(char* ptr)有效...


7
如果那本书的作者声称ptr = buffer;可以解决问题,你需要一本新书。 - StoryTeller - Unslander Monica
1
嗯,实际上让我们保持开放,因为这是来自一本书。 - Antti Haapala -- Слава Україні
3
@Wufo 是的,有这个问题,但我想抨击这本书。 - Antti Haapala -- Слава Україні
1
...以及作者... - Antti Haapala -- Слава Україні
3
我认为我们需要一个"黑名单"来列出糟糕的书籍,就像耻辱之殿那样。 - Lundin
显示剩余5条评论
2个回答

6

你的想法没有错 - 你是完全正确的。干得好,现在你比这本书的作者更有资格掌握C编程语言了。

这本书一文不值 - 这是第三版修订版,教授三十年前已过时的C版本,示例也极其失误。你只是在那个 test4 中碰巧很幸运。将数组第一个元素的地址放在那里只是为了抑制某些编译器中的警告,并且该数组恰好位于堆栈上的正确位置,没有被覆盖。但是 GCC 8.3 不会被使用中间变量所欺骗。


在函数中

char *test4(char *ptr){
    char buffer[10];
    ptr = buffer;
    strcpy(buffer, "testwert");
    return ptr;
}

在函数内使用ptr不会以任何方式影响函数外的指针。在原始示例中,它起作用是因为ptr仍然指向从堆分配的test3返回的值。当你用ptr = test4(ptr);替换它时,你将得到完全未定义的行为,因为ptr现在指向其生命周期之后的变量。当发生未定义的行为时,程序可能会做任何事情,包括(C11 3.4.3p1):

[...] 忽略情况,结果不可预测 [...]

“结果不可预测”包括可能按预期工作的可能性。


上一个公告中列出了其中一种选项,即

  • [Sie verwenden] einen beim Aufruf der Funktion als Argument übergebenen Puffer [...]

[您将使用]作为参数传递到函数中的缓冲区。对于这个选项,test4应该如下所示:

// use the **array** starting from *ptr
char *test4(char *ptr){
    // use a **different** string here so that you can verify
    // that it actually *works* (max 9 characters!)
    strcpy(ptr, "testval 4");
    return ptr;
}

甚至可能,或许。
void test4(char *ptr){
    strcpy(ptr, "testval 4");
}

在调用这个函数之前,必须有文档说明ptr应该指向至少包含10个char的数组。


升级了。这也是一个更广泛的事情的例子。缺乏诊断并不意味着没有问题。 - StoryTeller - Unslander Monica
谢谢您的解释。我会将该主题保存为“未定义行为”,并且不会像在test4中那样做(char * ptr)... ^^ - Wulf
通过一些谷歌翻译,这一部分实际上是在警告程序员不要返回分配在堆栈上的本地变量。 "Möglichkeit3" 只是粗心地编写 - 他们只是复制/粘贴了错误的函数 test1。我猜没有人校对过这本书。 - Lundin
顺便提一下,固定的 char* test4(char *ptr) 只有在将 main 改为 char buf[10]; ... test4(buf); 时才能正常工作。在作者的示例中,我们在 main 中有 char* ptr,即使使用了修复后的 test4,也无法正常工作。 - Lundin

4
char *test4(char *ptr) {
    char buffer[10];
    ptr = buffer;
    strcpy(buffer, "teswert");
    return ptr;
}

这段代码除了返回一个无效指针并没有做任何其他事情。你的理解是正确的,返回的堆栈指针是无效的,不能被读取。
之所以这样“可行”,是因为该指针实际上并未被使用。
test4(ptr);

传递了指针的副本并且返回值被忽略,所以它什么也没做。打印的文本来自于test3。换句话说,你可以更改那一个"testwert"并且得到完全相同的输出,如果你在test3中进行更改,那么两个打印都会发生变化。因此,换句话说,这本书犯了一个错误,并用另一个错误掩盖了它,然后由于测试代码非常不好,没有注意到所有的错误(如果不是"testwert"四次,错误就会显而易见,当然,任何值得一试的编译器都会发出警告)。

我建议抛弃那本书。


使用编辑过的ptr = test4(ptr)版本会导致未定义行为,因此可能发生任何事情。这包括打印预期的输出、打印垃圾、崩溃程序或更糟。


@Wufo,现在它只是未定义的行为,这意味着它可能会工作,也可能不会。对我来说,在 MSVC 上它不能工作(像第一种情况一样打印一些垃圾)。 - Blaze

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