Long post ahead. Just saying.
So, Python won't really help you here when learning C. A lot is hidden from you in Python, all of which is pretty important in C.
In C "everything is an array". Though this is an oversimplification and slightly inaccurate, it is nevertheless true. An array is literally a stack-allocated or dynamically-allocated pointer. A string is, also, an array of characters; as some article I once read put it, "Strings, in C, were an afterthaught". But I digress.
When a program is started on a computer (even an operating system/kernel of one) it is allocated a stack and a heap. The stack is where "local/global" static variables are allocated; that is, declaring a variable of type int named "i" (i.e. "int i = 2") creates a stack variable. The stack is also where functions are allocated and called. That is, a function is not allocated "on the stack", so to speak, but its "frame" is placed onthe stack. Every time a function is called, its "frame" is placed ("pushed") onto the stack, and when that function terminates its frame is removed ("popped") off of the stack. Hence, in recursive functions (functions that call themselves) there is a possibility of "stack overflow", that is, so many frames are pushed onto the stack that you fill the stack up, causing your next function call to be written somewhere else in the programs memory that is not the stack (because the stack is full). The heap is a huge chunk of memory set aside for each and every program executed on a computer. No program (bar the global program execution system, whatever that may be, of the OS) knows the "real" heap size; when a program is launched a chunk of the heap is allocated for that particular program. The heap, itself, however, is dynamically allocated and can change size depending on what the computer is doing at the time. The heap is used for dynamic memory allocation and it is accessible either through pointers, which I'll describe next, or through the memory management functions malloc, calloc, realloc, and free (though in C++, this is extended to add aligned_alloc and the C++ variants of the above four functions). Do note, however, that there are no C++ equivalents to calloc or realloc. In sum:
malloc() allocates memory and returns it to you. It does not do anything with that memory.
calloc can be used for allocating arrays, but can also be used to allocate ordinary blocks of memory. This function allocates, zeros, and then returns that memory to you. Its usage is a bit more complicated than malloc and there are very few places where I've ever seen it used.
realloc reallocates previously allocated memory. This includes shrinking allocated memory or expanding the allocation. It isn't difficult to use correctly though can be dangerous if misused.
aligned_alloc allocates an aligned block of memory. This probably won't be very useful to you right now.
free frees allocated memory. You should *only* use free on memory that has either been allocated by you or that you have been instructed to free by libraries your using. Just remember the rules of memory management -- which I'll outline below fater I describe pointers.
Now, pointers. Pointers are just special variables in a C program that point to memory addresses. The declaration:
Does not create an integer variable with the value 2500. Instead, it creates a pointer that is an integer that points to the memory address 2500. What value is at that memory address is unknown (since it is highly unlikely I have access to that memory address). There are reasons for this, involving paging and the way your processor manages memory, but I won't get into that unless you want to really know how that works. Typically you either utilize pointers by:
Allocating memory malloc, calloc) or receive allocated memory from a library; or
Creating arrays (which are also pointers) which are allocated at compile time.
The second use case is one of the most popular use cases. How it works is simple:
// Allocate a string
const char* text = "hello!";
char* text = "hello!";
Both of these declarations do the exact same thing: they set aside "stack memory" for an array of char with the characters 'h', 'e', 'l', 'l', 'o', '!'. The only difference is the first one, where I use the "const" keyword.
The first method is necessary with libraries and in situations where the information your manipulating most likely won't fit on the stack and/or is external to the program (i.e. the body of an HTTP request or a file from disk). In this instance, you need to use one of the above memory allocation functions. As an example, assume that we're loading a file from disk and we don't know the size (say, its passed to is by a caller). We might do:
FILE* f = fopen(filename, "r");
char* data = (char*)malloc(size);
fread(data, size, 1, f); // no error checking!
In this sample, we:
Open the file specified by the filename variable in "read" (text) mode.
Allocate size bytes of memory for our storage of the contents we're about to read.
read the contents of the file into the data variable using the fread function.
Close the file handle we've gotten from the OS using the fclose function.
Return the data we just read.
In this instance, the caller of our function must free the data that they've gotten from this, and so we should specify that in our documentation of this function. Which dovetails nicely into the rules of secure and safe memory management. For those, I refer you to chapter 9 of the SEI CERT C secure coding standards, first edition, for actual examples and in-depth analysis (including exceptions). I'll post them here though since your already reading this post (the summaries of them, really):
Allocate and free memory in the same module at the same level of abstraction
Store a new value in pointers immediately after free()
Immediately cast the result of a memory allocation function call into a pointer to the allocated type
Clear sensitive information stored in reusable resources returned for reuse
Do not perform zero-length allocations
Avoid large stack allocations
Ensure that sensitive data is not written out to disk
Ensure that the arguments to calloc(), when multiplied, can be represented as a size_t
Use realloc() only to resize dynamically allocated arrays
Do not assume memory allocation routines initialize memory
Use a pointer validation function
Do not access freed memory
Free dynamically allocated memory exactly once
Detect and handle memory allocation errors
Use the correct syntax for flexible array members
Only free memory allocated dynamically
Allocate sufficient memory for an object
Some of these rules you may not need right now (i.e. the flexible array members rule) but all of these are important and should be remembered when using memory management functions in your future adventures with C. The one about immediately type casting results of memory allocation to the type your actually using is not necessary so long as you've declared the variable as that type, as I've done above. The type cast ((char*)malloc(...)) is not truly necessary, though you should probably do it just to get rid of the warnings your compiler is bound to throw your way.
I hope I helped -- at least a little! And I'd be happy to explain something that I've written here that you don't understand in this post further too, of course.
"On two occasions I have been asked [by members of Parliament!]: 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out ?' I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question." — Charles Babbage.