Home > database >  What's the point of ''array = (int*) calloc(len, sizeof(int))'' instead of
What's the point of ''array = (int*) calloc(len, sizeof(int))'' instead of

Time:11-08

// Why would I use this
odd = (int *) calloc( nOdd, sizeof(int) );
even = (int *) calloc( nEven, sizeof(int) );

// When i can just use this
int odd[nOdd];
int even[nEven]; 

What is the point in calloc. I dont understand how it dynamically allocates memory when you need to input how many items are in the array.

Im used to python where you can just append to an array. So I would have thought it would be like that

CodePudding user response:

If I write the following, that memory only remains valid for as long as the function call is ongoing. It has automatic storage duration. We don't need to worry about freeing it, but it can't be used elsewhere without incurring the dreaded undefined behavior.

void foo(void) {
  int bar[100];

  // ...
}

If we dynamically allocate that array, then we do have to remember to clean it up, but we can return a pointer to it, and that pointer remains valid until the memory is de-allocated with free.

int *foo(void) {
  int *bar = malloc(sizeof(int) * 100);

  // ...

  return bar;
}

CodePudding user response:

The point of calloc or malloc is that your program can specify how much memory to allocate when the program is executing, instead of when you compile it.

Yes, C will not automatically resize arrays when you want to add more elements. That is something Python does for you. But somebody has to write the code to do that; memory will never manage itself. In Python, that may be done by writing C code to do the management, as well as most of the Python implementation. In C, you must do it yourself.

CodePudding user response:

C is not like Python.

C code is not (typically) run in an interpreter - it is (typically) compiled to native machine code and run as a standalone executable. Fixed-size arrays have to have their size known at compile time and cannot be resized at runtime. Variable-length arrays can have their size determined at run time, but they cannot be resized once they have been defined; the "variable" in variable-length only means their size can be different each time they are instantiated.

Thus, if we want to use memory that can be resized as needed, we have to use the dynamic memory routines malloc, calloc, and realloc. When your program is loaded, a segment of memory (often known as the heap) is set aside for managing dynamic allocation; each *alloc routines keep track of how much memory has been allocated and what regions are still free.

malloc and calloc allocate however many bytes you specify - calloc will zero out that memory.

realloc allows you to grow or shrink your dynamically-allocated buffers as necessary.

This is basically how Python works behind the scenes. It's just that the language is designed so all of this is transparent to you.

CodePudding user response:

What is the point in calloc

The point is simply to allocate a block of memory that your program can use for whatever you want. calloc() also zeroes out the block, which can be nice; malloc() does the same thing but the block will contain whatever garbage happened to be in that memory before.

What is the point in calloc. I dont understand how it dynamically allocates memory when you need to input how many items are in the array.

Dynamic allocation in this context means that you don't have to know what size block you need at compile time. It doesn't mean that you're creating a block whose size varies depending on what's in it. You can resize a block that you've previously allocated using realloc(), but you still need to know what size you want to end up with.

Note that you don't actually need to know how many items will be in your dynamically allocated array; you only need to know the maximum number that you might put in the array. You could allocate space for 100 numbers, for example, and then only actually add 75. The block will still be big enough for 100 numbers, and if you want to add more later, that's fine. It's your responsibility to make sure that you don't add more numbers than will fit in the array you allocated. Python probably does something similar behind the scenes: it'd be terribly inefficient to change the size of the array every time you add or remove something, so it probably makes its arrays a little bigger than they need to be, and actually resizes them when you exceed the available space.

Im used to python where you can just append to an array. So I would have thought it would be like that

C is not Python. It's a much lower level language, and AFAIK a heck of a lot faster partly because it doesn't do things like resize blocks of memory every time you add or remove data.

CodePudding user response:

You say:

// Why would I use this
odd = (int *) calloc( nOdd, sizeof(int) );
even = (int *) calloc( nEven, sizeof(int) );

In years long past (in another millennium), using calloc() was necessary if you wanted to determine the size of the arrays at runtime and could not risk allocating a fixed-size array and detecting when nOdd or nEven was bigger than the preallocated array sizes.

// When I can just use this
int odd[nOdd];
int even[nEven]; 

This is supported in C99 mandatorily; it is optional in C11 and C18. Each of these is a VLA (variable-length array) allocated on the stack (pace those who quibble about "the standard doesn't mention 'stack'"). You have to determine, somehow, whether there is enough space available on the stack to store those VLAs. This is the biggest objection to them. In C23, I understand that VLA allocation like this will be optional, but the ability to pass VLAs to functions will be mandatory. Such arrays will either be allocated statically (fixed size when defined, but a called function can handle different array sizes) or dynamically (via malloc() et al).

If there isn't enough space on the stack (if nOdd or nEven or both are very large), then you need to fall back on dynamic memory allocation. This means that the dynamic memory allocation works always, but you have to ensure you free the allocated memory when you're done. Using automatically allocated VLAs means you don't have explicitly free them, which is convenient.

When you check that the sizes are reasonable, using VLAs is also reasonable. But if the array sizes are too big, you need to fall back on the dynamic memory allocation.

CodePudding user response:

The salient distinction is in storage duration, which is kind of the topic of an entire chapter, so it might be best to pick up a good book. Suffice to say, I'll give it a shot, but you can't expect me to write better words than a decent textbook that's been peer reviewed by people who actuallu know wtf they're talking about (no offence to the few who do, but anyone can publish any drivel on StackOverflow, and there's not much by way of punishment for lying).

The following excerpts from the C standard form fundamental understanding of the four storage durations, which were chosen as an alternative to Python-style garbage collection. There are links embedded in the quotes that will take you to the document I'm citing. I'll also include an analogy to relate real life to each concept.

1. An object has a storage duration that determines its lifetime. There are four storage durations: static, thread, automatic, and allocated. Allocated storage is described in 7.22.3.

Imagine yourself working in a kitchen, with others, and depending on who you work with there may be some system for establishing chain of custody for equipment, to make sure nobody fights over resources and nobody uses anything too unsafe. We have four managers: Steve (static), Theresa (Threaded), Autumn (Automatic) and Allan (Allocated). Each manager has a different management system.

2. The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address,33) and retains its last-stored value throughout its lifetime.34) If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.

Equipment can end up in the bin or sink, at which point we can say any access to such equipment would be outside of its lifetime. In other words, if you use an object outside of its lifetime, this is kinda like using dirty and/or faulty kitchen utensils... generally risky and frowned upon. All of the managers agree on this, but the scope for which equipment is useful before it becomes dirty/damaged varies.

I'm going to use this simple function in all of my examples below.

void use(int *item) { item[0]  ; }

3. An object whose identifier is declared without the storage-class specifier _Thread_local, and either with external or internal linkage or with the storage-class specifier static, has static storage duration. Its lifetime is the entire execution of the program.

We'll come back to the _Thread_local thing later; that's Theresa's name tag (which isn't mentioned in your question) and in this paragraph we're actually talking about Steve's static equipment. As we'll soon see, your declarations for even and odd could be using Steve's static chain of command.

In Steve's kitchen, some pieces of equipment attached to a function labelled "static" retains its value for that function. You have a single knife and chopping board for chopping all of the onions, another knife & chopping board for chopping the mushrooms, etc. They're always in the same place and always for the same task. The equipment stays there so when you use the function, it's always the same equipment. This equipment won't go in the sink or the bin until the kitchen is shut down. To bring this analogy over to programming, the equipment has value that persists across uses; the first function call sets the value, the next accesses the value from the first, etc.

int *chop_shrooms_man(void) {
    static int knife;
    use(&knife); // knife is 1 after the first call, 2 after the second, etc
    return &totally_a_knife;
}

In the fragment of code above, it's okay to return a pointer to the knife, because the knife is always in the same place, always a knife, always has the last value assigned, etc.

Steve's other "static" equipment (which could be analogous to your declarations) is what common folk call "global variables". You can find just laying around the workplace, outside of any functions but everyone innately understands what it's to be reused for, so long as it doesn't have Theresa's _Thread_local label attached. In other words, your declarations are static if they appear outside of functions, without using the _Thread_local keyword.

int I_am_static[42];
int *chop_something(void) {
    use(I_am_static); // 1 after first call, 2 after second call, 3 after third call, etc
    return I_am_static;
}
  1. An object whose identifier is declared with the storage-class specifier _Thread_local has thread storage duration. Its lifetime is the entire execution of the thread for which it is created, and its stored value is initialized when the thread is started. There is a distinct object per thread, and use of the declared name in an expression refers to the object associated with the thread evaluating the expression.

With Theresa, each piece of equipment labelled _Thread_local is usable while it's attached to a thread of string. If the thread has been terminated (cut or severed), that equipment is garbage and we're not to use it. Suffice to say, the equipment you asked about isn't labelled _Thread_local, so you're not asking about Theresa's threaded storage duration.

_Thread_local int Theresas_equipment; // static and extern here distinguish between internal & external linkage, not storage duration

I'll leave this as an exercise for you to research in your time, as clearly it's not relevant here.

An object whose identifier is declared with no linkage and without the storage-class specifier static has automatic storage duration, as do some compound literals.

Finally, we come to Autumn. Her equipment is always confined to a function, like the first form of Steve's equipment we discussed, except that it's not labelled "static". Once the function (chopping something, as it were) is done, that equipment is then thrown in the sink.

int *chop_something(void) {
    int knife[42]; // automatic because it's in a function without any of those previously discussed keywords
    use(knife); // all good so far
    return knife; // THIS IS BAD!!!
}

In the example above, a pointer to knife becomes garbage when the function is returned, and so the caller is likely to access said garbage. This is a common error.


As far as Allan goes, he is perhaps the most liberal, yet tedious of them. Before you can use equipment, you have to have it "allocated". When you're done, you need to "free" the allocation. Anything that is "freed" becomes garbage.

int *example(void) {
    int *knife = malloc(sizeof *knife);
    use(knife);
    return knife; // all good so far
}

In the above code the function returns an allocated object within the lifetime, so it's okay for the caller to use the return value. OTOH,

int *example(void) {
    int *knife = malloc(sizeof *knife);
    free(knife);
    return knife; // THIS IS RETURNING GARBAGE!
}
  •  Tags:  
  • c
  • Related