0

I have already worked with the stack and the heap, and in the memory management topic generally, but there is a lot of thing's i can't understand

Like, if i'm allocating an array of integer using the heap with malloc and realloc how can i determine the exact size of the array i want to work with?

This Example:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[]){
    int *array = (int *)malloc(2);

    array[0] = 2;
    array[1] = 1;
    //What? i have allocated just 2 to be the size
    array[2] = 3;
    array[3] = 4;
    array[4] = 4;
    array[5] = 6;
    //there is no segmentation fault


    for(int i = 0; i < sizeof(array); i++){
        printf("%d\n",array[i]);
    }

}

And the wierd result i'm getting is:

2
1
3
4
4
6
1041 // ???
0

So, can someone explain to me how can i use malloc in the 100% correct way?

J. Murray
  • 1,460
  • 11
  • 19
Someone
  • 79
  • 10
  • 2
    Overrunning array boundaries is [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior). – Fred Larson Oct 01 '19 at 20:47
  • 2
    *You* are the one who knows how much you have allocated and *you* should make sure not to exceed it. C does not have this mechanism, it is not memory-safe language. BTW, it is not unique to heap allocation but any. – Eugene Sh. Oct 01 '19 at 20:48
  • 4
    `(int *)malloc(2);` does not allocate enough memory for even *one* 32-bit `int`. That's 2 **bytes**. I suggest `int *array = malloc(6 * sizeof *array);`. – Weather Vane Oct 01 '19 at 20:49
  • 2
    `sizeof(array)` [doesn't give you what you think](https://stackoverflow.com/q/492384/10077). – Fred Larson Oct 01 '19 at 20:53
  • 5
    *"Why no segmentation fault?"* If you poke a pointed stick around a room full of people and no-one gets blinded, it's because *nobody got poked in the eye.* – Weather Vane Oct 01 '19 at 20:55
  • 1
    undefined behavior is just that,, sometimes it manifests in a seg fault, sometimes it doesn't (you _hope_ it seg faults, so you can identify and fix it). Once you break the rules by invoking UB all bets are off. Do not waste your time trying to make sense of your results here. They can and will change with different compiler flags, different versions of compilers, different machines, run at different times, etc. – yano Oct 01 '19 at 20:56
  • @FredLarson So what can i use to determine the size of this heap array? – Someone Oct 01 '19 at 20:56
  • @Someone: See Joshua's answer, or the accepted answer to the question I linked in my second comment. – Fred Larson Oct 01 '19 at 20:57
  • The size of the heap array is `2`, the value you passed to `malloc`. – Weather Vane Oct 01 '19 at 20:58
  • @WeatherVane I know, but as you can see i used more than two element to fill the array, and no error was occured. – Someone Oct 01 '19 at 21:00
  • 1
    if `malloc` doesn't return a NULL pointer, then you have access to _at least_ as much memory as you requested (perhaps more, never less), but you should _always_ assume you only have as much memory as you requested. Accessing even a byte past it invokes UB. – yano Oct 01 '19 at 21:01
  • Possible duplicate of [I can use more memory than how much I've allocated with malloc(), why?](https://stackoverflow.com/questions/3509714/i-can-use-more-memory-than-how-much-ive-allocated-with-malloc-why) – dandan78 Oct 01 '19 at 21:59
  • ‍‍`int *array = malloc(2*sizeof(int)); printf("size: %zu\n",sizeof(array)/sizeof(array[0]));` – EsmaeelE Oct 01 '19 at 22:08
  • Above is true way of allocate two integer on heap and check the size of array variable. array now show 2, this is true on all systems. – EsmaeelE Oct 01 '19 at 22:10
  • Think of it this way: suppose that your array was holding some other object type. Say, for example, a `struct` that took a kilobyte all by itself: `struct Big { char data[1024]; };`. How will `malloc` know that you need an array two kilobytes large (to hold two objects of type `Big`)? Figure that out and you'll know what's wrong (shouldn't be hard). Hint: `malloc(2)` allocates enough memory to hold two `char` objects, i.e. you could copy `char data[2]` into that malloc-ed area. Two bytes. Vs. two kilobytes :) – Kuba hasn't forgotten Monica Oct 01 '19 at 22:17

2 Answers2

4

To correctly allocate an array using malloc, use sizeof to determine the size of each element in your array, then multiply by the number of each that you need.

Your code is only allocating 2 bytes of memory in heap, so when you write these integers (which take 4 bytes each on my machine), you are overwriting the values of unrelated state within the heap located beyond those two bytes, thus corrupting the machine state and creating undefined (that's bad) behavior.

In addition, your for loop was looping on the size of array pointer, which is typically 8 bytes. So your for loop would have tried to walk over 8 int elements, in an array of 6 ints in which you had only allocated 2 byte instead of the 24 bytes needed. Lots of bad undefined behaviour to go around here!

You may or may not get a segmentation fault due to this. A segmentation fault means you are dereferencing a pointer to an invalid page (segment) of memory, and this is caught by the hardware.

When you corrupt memory, you may not see the result of your error immediately as a segmentation fault if the memory you are writing is valid memory. Worse, if you corrupt the stack or a pointer, it may take a long time to get an actual fault to help detect the corruption created. This makes it hard to connect the fault to the event that caused the exception since your code could run for a long time before getting a segmentation fault.

#include <stdio.h>
#include <stdlib.h>

#define NUM_INTS_WANTED   6

int main(int argc, char* argv[]){
    // No more trampling memory since array allocated to correct size       
    int *array = malloc(sizeof(int)*NUM_INTS_WANTED);

    // Always check if malloc succeeded by checking that pointer is not NULL
    if (array != NULL) {
       array[0] = 2;
       array[1] = 1;
       array[2] = 3;
       array[3] = 4;
       array[4] = 4;
       array[5] = 6;

       // No more seeing 8 ints since you stop after 6 ints now
       for(int i = 0; i < NUM_INTS_WANTED; i++){
           printf("%d\n",array[i]);
       }
    } else {
       // malloc failed! Report it.
       printf("malloc failed!\n");
    }
}
ScottK
  • 1,526
  • 1
  • 16
  • 23
  • 1
    @Macmade My bad. My ints are 4 bytes. I typed out the answer too quickly. Pointers are 64 bit (8 bytes), ints are 32 bits. Thanks for the correction. – ScottK Oct 01 '19 at 21:49
  • 2
    `int *array = malloc(sizeof *array * NUM_INTS_WANTED);` (if you always use the dereferenced pointer to set the type-size, you will never get it wrong `:)` Then `if (!array) { perror ("malloc-array"); return 1; }` (always ***validate*** each allocation) – David C. Rankin Oct 02 '19 at 00:54
  • @DavidC.Rankin That's excellent advice regarding checking if the malloc succeeded. I would be concerned with using sizeof(*array) because it makes the code appear more general than it really is. If *array is an non-word number of bytes such as three bytes, then the malloc calculation may not take into account element alignment. I know of solutions that are compiler specific, but I thought that this was probably would over complicate the answer for the user. Would be interested in hearing your thoughts on making malloc tolerant of element alignment issues. Perhaps calloc would be more general? – ScottK Oct 02 '19 at 03:16
  • 1
    @ScottK *If \*array is an non-word number of bytes such as three bytes, then the malloc calculation may not take into account element alignment.* Not true. Per [**6.5.3.4 The sizeof and _Alignof operators**, paragraph 4](https://port70.net/~nsz/c/c11/n1570.html#6.5.3.4p4) of the C11 standard: "When applied to an operand that has structure or union type, the result is the total number of bytes in such an object, including internal and trailing padding." If your three-byte object has a 4-byte alignment requirement, it will have one trailing byte of padding and `sizeof()` will return 4. – Andrew Henle Oct 02 '19 at 17:11
  • @AndrewHenle Thanks! I did not know that. My C usage predates C11. Started using it in the 1980s and our compiler allowed us to set alignment properties on individual variables. Good to know this is now standardized in C11 – ScottK Oct 02 '19 at 18:15
0

You can't. It's impossible.

Once upon a time a certain heap manager exposed this information by providing another function that would indeed return the real size. (It's padded up to the next block size.) They took it out after it was discovered to have caused more bugs than it prevented.

On most heap managers, the real size in bytes can be found at a small negative offset, but not all of them. Don't write this code. You will regret it if you have to maintain it. The heap manager can be swapped out from under you and you won't know what went wrong.

Joshua
  • 40,822
  • 8
  • 72
  • 132