2

I'm learning C and using the free() function lead me to this question. Say I have a pointer:

char *p = "String";

Now 7 bytes of memory have been allocated for p to point to. However, sizeof(*p) returns a 1 which makes it seem like the pointer only "knows" about the 1 byte whose address it holds.

If this is the case, then how does free(p) free up all 7 bytes. So the only things I can think of are:

  1. Something is happening behind the scenes

  2. Only the first byte that is being pointed to needs to be deallocated

Is it one of these? If it's 1. what is the hidden process/how does it work? If it's 2. then how does the machine keep track of the 6 other bytes that should not be allocated for other pointers and variables?

Thanks for the help!

  • In your case, there is no any memory allocated for p, it's just string literal. – moeCake Dec 11 '13 at 01:25
  • 2
    `p` is not allocated with `malloc()` or his friends, so you can't call `free()` on it. – Yu Hao Dec 11 '13 at 01:26
  • That being said, had it been allocated with malloc, free "knows" how much memory to free because there's some bookkeeping behind the scenes that you don't see. – Dennis Meng Dec 11 '13 at 01:26
  • In general C language design philosophy prohibits the "behind the scene" stuff. It combines the performance of the assembly language with the flexibility of the assembly language :-) – Sergey Kalinichenko Dec 11 '13 at 01:27
  • @dasblinkenlight I immediately thought of http://stackoverflow.com/a/236770/1476062 when you posted that. :P – Dennis Meng Dec 11 '13 at 01:29
  • 1
    And the sizeof(*p) is just like sizeof(char), which is of course 1. – moeCake Dec 11 '13 at 01:29
  • Ok say I didn't use free() and just let the program terminate, then the machine automatically deallocates the array of chars being pointed at, how does it know to get all 7? This isn't even visible at the assembly level because all we use is the ret command. Does this mean it's determined at a hardware level? –  Dec 11 '13 at 01:36
  • 1
    The answers seem to conflict a bit. –  Dec 11 '13 at 01:37
  • Also, what if it wasn't a string. Say it was a pointer to an array of int pointers, you can't use NULL to find the end of that –  Dec 11 '13 at 01:41
  • There is a piece of code called the `loader` that allocates the memory that is "known" at the start of the program (string constants, static variables, etc). All the memory space set aside for the program is claimed from the kernel by the loader - and when the program says "I'm done" the kernel takes it all back - without worrying about how it had been allocated. Typically it is not even zeroed out - just "made available for the next program that wants it". Which is why you will sometimes find "garbage" in memory locations that you didn't specifically initialize. – Floris Dec 11 '13 at 01:48
  • Ok, so then is the point of malloc() and free() to keep that memory space clean for long running programs that might use it all up by not deallocating? According to one of the answers, malloc() takes more space than needed for just the raw data and attaches meta data so that free() can use that info to deallocate the right space. Is this correct? –  Dec 11 '13 at 01:53
  • Yes it is. See http://stackoverflow.com/questions/15368093/is-there-a-way-to-know-size-of-a-pointer-passed-to-a-free-hook-in-linux/15368372#15368372 for some more information about this (as implemented on Linux - but other implementations do exist). – Floris Dec 11 '13 at 02:29
  • Calling `free()` on the OP's `p` would provoke undefined behaviour. – alk Dec 11 '13 at 07:38

2 Answers2

1

sizeof(*p) is 1 because p points to a character (which is one byte).

"How does it know" how big the allocated memory is - it's "platform dependent". Very often you will find two things about malloc:

  • It allocates a bigger block than you ask for - because it's more efficient to work with, say, 64 bytes at a time (alignment issues and such)
  • Often, there is a small block of code "just below" the pointer that contains some information about the size of the block, and pointers to the "next and previous blocks" - like a linked list. That way if you clear one block, the memory management can keep track of what is free and what is not.
  • It is also possible that a allocation table is being maintained separately that describes what blocks are free / used, how big they are, etc.

NOTE - what I am describing above relates to memory allocate with malloc - which is the only kind that C can free(). A string constant is allocated in a different way, and its size and position are fixed - which is why you would usually write

const char *p = "hello";

For such a string, you know the size by looking for the nul terminator. But if you actually decided to overwrite a character earlier in the string with '\0', it does not change the amount of memory allocated:

char p[] = "hi there world";
p[2] = '\0';
printf("%s***\n", p);

output:

hi***

but the memory is not freed up.

Floris
  • 45,857
  • 6
  • 70
  • 122
  • i think this is the only answer that addresses the intent of OP's post (memory allocation bookkeeping), even if he accidentally gave a string literal in his example – im so confused Dec 11 '13 at 02:09
  • There is an informative thread [here](http://www.embeddedrelated.com/usenet/embedded/show/79748-1.php) – Floris Dec 11 '13 at 02:25
  • Note: `p[2] = '\0'` does not necessarily change anything. `printf("%s***\n", p)` could still print `"hi there world"`. – chux - Reinstate Monica Dec 11 '13 at 03:05
  • 1
    @rici UB is UB. A classic potential case here is that `"hi there world"` is placed in a read-only memory and changes are ignored. – chux - Reinstate Monica Dec 11 '13 at 03:22
  • @chux you are of course right. I changed it to `char p[]` so my example actually works. If anyone else is wondering what's up, read http://stackoverflow.com/questions/1704407/what-is-the-difference-between-char-s-and-char-s-in-c – Floris Dec 11 '13 at 03:23
1
char *p = "String";

This is legal, but it really should be:

const char *p = "String";

so you don't accidentally try to modify the array.

What array, you ask? Any string literal corresponds to a statically allocated array of size LEN+1, where LEN is the length of the literal. So in this case, there's an anonymous array of type char[7] that exists during the entire execution of your program, containing the values

`{ 'S', 't', 'r', 'i', 'n', 'g', '\0' }`.

The initializer for p causes it to point to the first element of that array, which contains the 'S'. And since the array was not allocated by a call to malloc() (or calloc(), or realloc()), you must not attempt to free() it.

Given p, you can't directly determine the size of the array (since p points to its first element, not to the array as a whole). You can compute strlen(p) + 1, which is probably the same as the allocated size -- but if the string literal were "foo\0bar", then it would be 8 bytes long, but strlen would stop at the first null character and return 3. So if you need to be able to determine the size of the array, don't discard that information by assigning its address to a pointer.

Now if you had instead written:

char arr[] = "String";

the initializer would copy the contents of the string literal into the array arr, and the size of arr would be determined by the compiler from the size of the initializer. Again, you must not attempt to call free() on this array (or rather, on a pointer to its first element), since it wasn't allocated with malloc(). But you can easily determine its size via sizeof arr, which will yield 7. (sizeof p would give you the size of a pointer, not of the array it points to.)

Finally, if you used malloc() to allocate an array:

#define message "String"
char *p = malloc(sizeof message + 1);
if (p == NULL) {
    fprintf(stderr, "malloc failed\n");
    exit(EXIT_FAILURE);
}
strcpy(p, message);

you still can't use p (which is just a pointer to a single character) to determine the size of the allocated array -- but free() is able to figure it out. How? The language doesn't say, but any implementation will keep extra bookkeeping information behind the scenes to let free() do the right thing.

Note that the implementation doesn't necessarily remember the size you requested. A call to malloc(7) might actually allocate, say, 16 bytes, and the only information free needs is the base address and the actual allocated size.

But since you wrote the call to malloc, if you need to remember the allocated size, just save it somewhere:

// ...
const size_t allocated_bytes = sizeof message + 1;
char *p = malloc(allocated_bytes);
// ...

If you have a declared array object, you can use sizeof to determine its size. Arrays are usually manipulated using pointers, which do not retain the size of the array; you have to keep track of that yourself. malloc() and friends do maintain some size information behind the scenes, but only for the purpose of letting free() work correctly; you (probably) can't access that information yourself. If you need to keep track of the size of an allocated array, you have to do it yourself.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631