You are not alone. I picked up enough C syntax to write trivial programs like the "guess the number" game in it around age 13 or so, and I already understood the concept of a pointer or memory address from reading about (but not actually practicing until I was older) assembly language and old game consoles. The questions "what the hell are headers, how am I supposed to separate a program into multiple files, how am I supposed to structure my program?" kept me from even trying anything in C until I was 17, and even then it took months of fiddling/guesswork and finally stumbling across Learn C The Hard Way before I had any idea of what I was supposed to be doing instead of throwing everything in a single giant source file. I'm still rarely sure about where I'm supposed to draw the line, and languages like C that use headers to provide and control interface and visibility make it such a pain in the ass to refactor that I err on the side of large source files.

Since you seem to be asking, headers in C are mainly used to provide interfaces to functions and datatypes defined in other source files. It's basically C's kludgy way of marking things as public or private, and choosing what external functions a source file can see.

For each source file other than the main one, you write a separate header file that includes declarations of the functions in it (just `int dostuff(int arg);` without the actual implementation in curly brackets), and definitions of the publicly-visible datatypes used in it (so your struct definitions usually go in the header, not the source file). You control whether or not something is visible externally or not just based on whether or not you write a declaration or definition for it in the header, and you control which functions are visible to each source file based on which headers you include.

In a separate source file, you include the header of every source file that contains functions you want to use. Now, the compiler knows the signature of those functions, and when you write `some_function_in_another_file();` you can think of it as "setting aside space" for the function call, even if the compiler has no idea what code is actually contained within it. It just sets up the parameters it is going to pass to it, and relies on the assumption that the function will follow the rules and return a value in the right place that it can go on to use.

EDIT: I should add that the whole purpose of making the function declarations visible is so that compiler knows how to arrange the stack. It can't know what kind of code to generate unless it has some idea of what arguments the function will be passed, and what the state of the stack will be after the function is called. I'll also add that it's important to understand the difference between definitions and declarations

All source files are compiled separately into object files. When the executable is created, a program called a linker ties up the loose ends; for example, it would decide on an address to store `some_function_in_another_file()` at, and replace the "function call stub" I mentioned earlier with the code to actually call this function.

Libraries will generally use include to generate a "super header" that a user include with a single include statement and get access to every function and data type in the library. In your own code, however, it's usually better to explicitly include only the particular headers that a source file uses, only generating "super headers" for large internal modules that will be treated a bit like external libraries by the rest of your program. For example, if you were making a game, and you wrote all of your own code to draw graphics in software, it would probably smart to make a "graphics.h" that includes all of the headers in the graphics module, so that other code can access all of it with a single include. Within the graphics module, however, you would only explicitly include the certain source files that each file needed to access. At least, that's how I would do it.

You should also include each source file's header in the source file itself; if you wrote your header correctly, it will work just fine, and you can use this to catch discrepancies between the source file and the header early on.

In an ideal world, there would be no need for this. We'd have a smarter language that allowed you to just mark each datatype or function as public or private in the source file, and instead of including a header, you'd import from a source file, or import from a namespace or something. That's just the way C is, unfortunately.

If that didn't help, read Learn C The Hard Way. Opinions on it differ, but it really helped me get an understanding of problems like header files that were forming the real and frustrating roadblocks in the way of doing anything interesting in C.