Return Pointer from function with pointer as param
Asked Answered
I

2

6

I'm reading this book: "C von A bis Z".

There is this example.

/* 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;
}

I'm understanding the problem the author is talking about.

Why the test4 solution is working?

If I am understanding correctly, doesn't it

  1. allocate char buffer[10]; on the stack
  2. assign the address of the first element of buffer to my ptr (living in previous scope) ptr = buffer;

my expectation:

Point with ptr on buffershould be false, because this scope should be broken/cleaned up.

What is wrong with my thinking?

EDIT 1

I changed test4(ptr); to ptr = test4(ptr) and it still works...

Still don't know why test4(char* ptr) is working...

Insignificant answered 1/4, 2019 at 7:29 Comment(10)
Which one you refer as 3rd solution?Dincolo
char buffer[10]; the memory stops existing after }. You return a pointer to nonexistent memory. I can only guess that in possibility 3 it is expected that ptr is a pointer to a valid memory before you call test4.Sheol
If the author of that book claims ptr = buffer; fixes the problem somehow, you need a new book.Khabarovsk
Also remember that in C all arguments are passed by value, i.e. the value is copied into the argument variable. The argument variable is itself local to the function, and all assignments to it will be lost as soon as the variables life-time ends together with the function.Hufford
Hmm, let's keep this open actually because it is from a book.Ennius
@AnttiHaapala yeah, because I think the is still no answer of the real question...Insignificant
@Wufo yes there is but I want to berate the book.Ennius
... and the author...Ennius
@Wufo good work, if you read everything with this critical mindset you'll become a stellar C programmer.Ennius
I still think we need a SO "blacklist" with bad books. Hall of Shame kind of thing.Noah
A
6

Nothing is wrong with your thinking - you're absolutely correct. Good work, you're now more qualified in the C programming language than the author of the book.

The book is worthless - 3rd revised edition, and it teaches the antiquated version of C from 3 decades ago with horribly broken examples. You just happened to be lucky with that test4. Putting the address of the first element of array just suppresses the warning in some compilers, and the array happened to be in the correct position on the stack and not being overwritten. But GCC 8.3 isn't fooled by using an intermediate variable.


In the function

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

using the ptr within the function does in no way affect the pointer outside the function. It worked in the original example because the ptr was still pointing to the value returned from test3, which was allocated from heap. When you replace it with ptr = test4(ptr); you'll get wholly undefined behaviour, as ptr now points to a variable past its lifetime. And when undefined behaviour happens, then the program might do anything, including (C11 3.4.3p1):

[...] ignoring the situation completely with unpredictable results [...]

with "unpredictable results" including the possibility of it working "as intended".


The previous bulletin point lists one of the options as

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

i.e. [You'll use] a buffer passed as an argument into the function. For this option, test4 should read

// 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;
}

or even perhaps

void test4(char *ptr){
    strcpy(ptr, "testval 4");
}

with documentation telling that prior to calling this function ptr should point to an array of at least 10 chars.

Apiece answered 1/4, 2019 at 7:49 Comment(4)
Upped. And it's also an example of a broader thing. Lack of a diagnostic does not mean lack of a problem.Khabarovsk
Thank you for the explanation. I will save the topic as "undefined behavior" and will not do it like in test4 (char * ptr) ... ^^Insignificant
With some Google translate, this section is actually all about warning the programmer about returning local variables allocated on the stack. "Möglichkeit3" is simply sloppily written - they just copy/pasted the erroneous function test1. Guess nobody proof-read the book.Noah
Btw please note that the fixed char* test4(char *ptr) will only work if main is changed to char buf[10]; ... test4(buf);. In the author's example, we have char* ptr in main which won't work even with the fixed test4.Noah
C
4
char *test4(char *ptr) {
    char buffer[10];
    ptr = buffer;
    strcpy(buffer, "teswert");
    return ptr;
}

This code doesn't do anything else than returning an invalid pointer. Your understanding is correct, the stack pointer returned is invalid and shouldn't be read.

The reason why this "works" is because that pointer isn't actually used.

test4(ptr);

A copy of the pointer is passed and the return value is discarded, so it does nothing. The printed text is from test3. Case in point, you can change that one "testwert" and the print you get is the exact same, and if you change the one in test3 it changes both prints. So in other words, the book makes a mistake and hides it with another mistake, and then it doesn't notice all the mistakes because of how poorly it tests the code (if it wouldn't be "testwert" four times, the errors would be apparent, and of course any compiler worth its salt will issue a warning).

I recommend trashing that book.


With the edited version of ptr = test4(ptr) it's undefined behavior, so anything might happen. This includes printing the expected output, printing rubbish, crashing the program or worse.

Cherish answered 1/4, 2019 at 7:35 Comment(1)
@Wufo now it's just undefined behavior, which means it might or might not work. For me on MSVC it doesn't work (it prints rubbish like in the first case).Cherish

© 2022 - 2024 — McMap. All rights reserved.