18

Answers to questions in your head: Yes, this is for school. No, I can't use threads for this. And yes, I looked for an answer and some people said "yes" and others said "no." I'm also fact-checking my professor because I don't want to unfairly lose points if someone else were to grade it and they require this to be "fixed."

With that being said... consider this simple program for c on the Linux system. I malloc something and then fork. I boiled down my project to the exact problem:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main( void )
{
    char * args [] = { "someinvalidcommand", NULL };

    // malloc before the fork (happens in parent process)
    char * something = (char *)malloc(sizeof(char));

    pid_t child_pid = fork();

    // are there now two things that need to be freed:
    // one for each process?

    if(child_pid == 0) // child process
    {
        //free(something); // is this needed?

        // execvp (it won't return if succeeded)
        if(execvp(args[0], args) < 0)
        {
            // or do I only need to free it here?
            printf("%s: No such file or directory\n", args[0]);
            /*
             * EDIT: Calling it here seems to fix the issue.  It turns out
             * that the system calls were the ones with the "still reachable"
             * errors, so I guess it's not all that important that the
             * memory be freed.
             *
             * free(something)
             */
            _exit(1);
        }
    }
    else // parent process
    {
        int status;
        while(wait(&status) != child_pid);
        if(status != 0)
        {
            printf("command status: %i\n", WEXITSTATUS(status));
        }
    }

    free(something);
    return 0;
}

Now this is where it's slightly confusing. To my knowledge, fork creates an exact copy of the parent process at that particular state (including text, data, etc.). I read somewhere that this includes anything malloc'd (so, the heap). However, I read somewhere else that it doesn't because of something called "copy-on-write," but then I read somewhere else that "copy-on-write" is simply an optimization that's transparent and irrelevant. But then what I read that made the most sense was that since it's a COPY, it has its own, well... everything.

But then I recall that when fork() is used, whatever was malloc'd will contain the same memory address, so are the parent and child pointing to the same thing? Do I need to free resources in the child, as well? Are only the pointers copied, or are the data that the pointers are pointing to also copied?

I used valgrind and when the child process exits, it simply complains that all the memory is still reachable. How exactly is it "still reachable?" Does the fact that it's "still reachable" answer my question and say that the parent and child are pointing to the same thing and the parent's the only one responsible for freeing the memory?

user3598200
  • 181
  • 1
  • 1
  • 5
  • This thread explains, http://stackoverflow.com/questions/4597893/specifically-how-does-fork-handle-dynamically-allocated-memory-from-malloc?rq=1 – M.M May 03 '14 at 04:17
  • I'd suggest adding your OS into your comment or tags – M.M May 03 '14 at 04:22

3 Answers3

8

In the absence of the calls to the exec family, you have to free() it. Parent and child do not point to the same thing, because they are separate processes and do not share the same address space. Imagine what would happen under the alternative if the parent free()d it, for instance, and then the child tried to access it.

If you do call something like execvp(), then as tmyklebu mentions, your process just gets wiped, and you don't have to do anything.

"Still reachable" means you still have a reference to it, but you haven't free()d it yet. Since all your memory gets free()d on termination anyway, this sometimes isn't so much of a problem, compared to getting an actual memory leak where you permanently lose track of allocated memory. Valgrind's FAQ itself says that "your program is probably ok -- it didn't free some memory it could have. This is quite common and often reasonable." Opinions on the subject differ - some folks say it's good form to explicitly free() everything, others say it's a pointless waste of resources to do what program termination is going to do for you all by itself.

Crowman
  • 25,242
  • 5
  • 48
  • 56
  • You don't understand how `fork` works. If the parent `free`s an object post-fork, the mapping still exists in the child process and the mapping refers to a copy of the object at the time of the `fork`. – tmyklebu May 03 '14 at 04:16
  • 2
    @tmyklebu: That's exactly what I said. "Parent and child do not point to the same thing ... Imagine what would happen under the alternative." – Crowman May 03 '14 at 04:18
  • Gotcha. You're working under the assumption that OP does not want any allocated memory reachable at the time of the `exec`. Your answer makes good sense in that context. – tmyklebu May 03 '14 at 04:20
  • @tmyklebu: Yeah, I completely missed the `execvp()` call in his code the first time through. – Crowman May 03 '14 at 04:22
  • Yeah well part of the assignment is to use malloc and free correctly, which is why I'm asking. I tested malloc and _exit together and I get the same "still reachable" error (in a program without fork) so it may be something for me to worry about. – user3598200 May 03 '14 at 04:32
  • @user3598200: You'll get that (it's not really an "error") every time your program terminates with memory you haven't `free()`d, regardless of whether or not you `fork()` anything. Some say it's good form to explicitly `free()` everything, others say it's a pointless waste of resources to do what program termination is going to do for you all by itself, so you can take your pick. It's only a serious issue if you have runaway memory allocation - if you're retaining references to memory you don't need anymore, sooner or later you're going to run out. – Crowman May 03 '14 at 04:36
  • Here's [what Valgrind has to say on the matter itself](http://valgrind.org/docs/manual/faq.html#faq.deflost) - "your program is probably ok - it didn't free some memory it could have. This is quite common and often reasonable." – Crowman May 03 '14 at 04:47
  • Well here's another question (which I'll probably put on the original post): what if what I pass into execvp is malloc'd? How do I possibly free that memory? Cuz according to the responses I got here, I will probably have to free everything that's been malloc'd. – user3598200 May 03 '14 at 04:51
  • It'll be fine. By the time `execvp()` wipes your address space clean, it doesn't need the memory for its arguments anymore. – Crowman May 03 '14 at 04:53
  • Yeah I played around with it and it seems impossible to not get valgrind to complain about the reachable memory in the child if the string to pass into an exec command itself needs a malloc. In order to move it to the stack I need a "char[][] args". But I need "char * args []" for execvp, and the * will require a malloc. Casting char[][] to char * [] in execvp doesn't work, either. – user3598200 May 03 '14 at 05:14
  • 1
    You never "have to" free anything. – R.. GitHub STOP HELPING ICE May 03 '14 at 05:24
  • 1
    You have to free them if you want them to be freed. The context of the question is clearly does the child need to free them, or will the parent free them for you? And the answer is no, the parent won't free them for the child, if you want them free in the child, the child has to free it. – Crowman May 03 '14 at 05:38
  • So it's impossible to free them, then, because what goes into the execvp is determined at runtime. And I do "have to" free them, because the context of "have to" in this case refers to the requirements that I have to follow with managing the memory. We don't "have to" free anything, but we don't "have to" do anything, really. – user3598200 May 03 '14 at 06:03
  • Are you using `--trace-children=yes` when you invoke Valgrind? – Crowman May 03 '14 at 06:17
5

execvp wipes your address space, so the allocated memory is gone.

_exit exits your program, meaning the allocated memory is gone.

You do not need to explicitly free anything in the child process; in fact (because of the COW thing) it's not a bright idea to do so.

tmyklebu
  • 13,915
  • 3
  • 28
  • 57
  • You seem to be basically saying that since the OS frees anything when `_exit` happens anyway, OP should not worry about freeing stuff. – M.M May 03 '14 at 04:19
  • 1
    @MattMcNabb: Yes. Furthermore, you don't want to write to memory you don't have to touch (since that incurs a copy), so `free`ing your pointers before an `exec` is a readability and performance hit with no gain. – tmyklebu May 03 '14 at 04:22
  • What if you're going to keep the child process around for a while with no exec(), doing stuff more-or-less independent of the parent? – SamB Sep 06 '16 at 21:41
  • @SamB: Then the OS is going to do a bunch of copying every time either parent or child writes to some page it hasn't before. Meaning you probably see a substantial performance hit. Why would you want to do that? – tmyklebu Sep 09 '16 at 05:23
2

Calling free before execvp is pointless, and it actually makes your code less reusable/portable, since it's not valid to call free in the child after fork if the calling process is multithreaded.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Eh? Not valid how? – SamB Sep 06 '16 at 21:41
  • 1
    @SamB: For a multithreaded process that forks, only async-signal-safe functions may be called in the child after fork. Calling any async-signal-unsafe function results in undefined behavior. – R.. GitHub STOP HELPING ICE Sep 07 '16 at 03:32
  • Oh, I see, POSIX actually the reason for this pretty succinctly: look for "* A process shall be created with a single thread. [...]" on http://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html – SamB Sep 09 '16 at 01:14
  • @SamB: Yes. Conceptually, since memory in the child comes from some instantaneous state of the parent, where other threads may have been in the middle of async-signal-unsafe functions, and since those threads no longer exist to make forward progress, the environment of the child is similar to what you would get if you forked from a signal handler that had interrupted those AS-unsafe functions. – R.. GitHub STOP HELPING ICE Sep 09 '16 at 01:40