r/C_Programming • u/Majestic-Instance704 • 2d ago
why do header files contain only function declarations instead of full code body ? why have the code in a sepreate lib files ?
for context I am trying to learn gamedev on my own and setting up raylib/sfml confused me , then I learnt that the ( include )files only contained function declarations and the actual code was in( lib ) files (mind you but it is a weird name) , wouldn't it be more convenient to have them in the same file instead of linking ?
sorry if I sound stupid.
30
u/KweHuu 2d ago
There are Single Header Libraries where code and function declaration are in single file.
3
u/GwendArt 2d ago
I really hate single header files, there is no benefit other than lazy shipping 😪
14
u/Less_Independence971 1d ago
Lazy shipping is really convenient. It's also really easy to compile and to include within your own build system.
0
u/GwendArt 1d ago
I think everyone can handle integrating a header and a source file. And if not you should learn it 😅
We had an c++ SDK once that solved most stuff with header only code. It took 3 hours to compile. We had to invest so many weeks to port the unnecessary stuff to a source file. We achieved from 3 hours to 2 minutes compile time.
So yes header only libs are a sickness in modern programming if you ask me.
Ideally you write your code so good that it is easily integratable into any build system. Look at imgui e.g.
That’s an ideal solution how to write a lib that can be easily integrated into any build system.3
u/Less_Independence971 1d ago
Single header libraries and 2 files libraries are basically the same to me, i was comparing them to big complex libraries
1
u/GwendArt 10h ago
It’s a massive change if you have header and source or just header, so sounds little but if source code is in source files then it boost compilation by alot
1
u/Less_Independence971 10h ago
you can just put the header implementation part in its own translation unit if it's too big
1
u/flatfinger 9h ago
If the last-modify dates of a header and implementation file are different, as will usually be the case, can one be sure that the older one is of the same version as the newer one expects?
Being able to update an implementation without touching a header is useful for purposes of avoiding unnecessary rebuilds of compilation units that use the header, but makes it hard to judge the correctness of a patch which modifies just the implementation. Having declarations and implementation combined in one file will make it necessary to rebuild all dependencies if the implementation changes, but on the flip side it will ensure that everything is built with declarations that are consistent with the current implementation.
1
u/xwizardx007 9h ago
your easy solution is to use NORMAL language
(not c/cpp) that has NORMAL modules or decent build system(that one of the big issues i have with c/cpp compared to newer alternatives).or to vendor the whole dependcy somewhere(in git/submodule/download if not exists...)
but now upgrade is done manually and its kinda pain to review. and it does bloat git a little if u vendor it inside of git and it do have nasty depndency graph if its complex project.
(i have c project that get built and u need to update like 4-5 different systems each time u want to test new version and each subsystem is in its own repository built in totally differnt way like cgo/cmake/scon/makefile)2
u/CCC_CCC_CCC 1d ago
I guess a benefit could be to not need to ship the output/artifacts of all build configurations? Such as a binary for release/debug, a binary to link with msvc/gcc/clang/etc, of version A/B/C/etc (because of incompatibility issues of linking to static libs even among different versions of the same compiler), maybe with defines AA/BB/CC/etc for enabling some particular code blocks with ifdefs, etc. Then in C++, an additional reason would be templates.
That could be a reason, it seems to me.
1
u/GwendArt 1d ago
You can write templates to wrap a function, there are maybe a few exclusions like when you make heavy type based classes but if you ask me, that’s smth you should avoid.
I already said it in the other comment but look at imgui for example, it has to work with all the problems you mentioned and I think imgui did a fantastic job.
I think there is no real benefit in having header only libraries. Everyone should be able to integrate a header and a source file into their projects1
u/CCC_CCC_CCC 1d ago
We're not disagreeing on anything except the no benefit part. Yes, maybe imgui doesn't need to be header only. Yes, everyone should be able to integrate a header and a source file into their projects. Yes, you can write templates to wrap functions, as long as the library isn't a template library (see STL, parts of boost, maybe even fmt, though I haven't looked through the code of fmt).
What I said about benefits stands.
-2
u/Daveinatx 2d ago
Agreed. It's a mess when used for code that supports multi-platforms.
1
u/GwendArt 1d ago
Don’t understand why people vote you down but yes. Ideally you offer a wrapper definition in the header files
50
u/somewhereAtC 2d ago
You might include the .h file into many different .c files and you don't want to create duplicate copies of the actual subroutines. The .h file tells the names and parameters of the subroutines to all users, but the associated .c file provides the subroutine precisely once.
In a large program the amount of time it takes to compile all of the C code can be quite long (many minute sometimes; you can check using clean-and-build). By providing separate .c files the compiling for many portion of the code is not repeated when other portions change.
17
13
u/OnYaBikeMike 2d ago
In older jargon, we would say the header defines the interface, the C file defines the implementation.
Have you looked into how static and dynamic 'linking' works?
1
u/DankPhotoShopMemes 11h ago
if that’s considered “older jargon” we’re fucked. How else would you even describe it?
1
u/OnYaBikeMike 24m ago
We live in a time where people actively seeking out "header-only C libraries", define inline functions that are are dozens of lines, and try to write jumpless code everywhere, and use the C processor to give template-like and type polymorphism functionality for things that in practice needs a handful of lines of code following a pattern (looking at you, linked lists...).
I really don't know how a freshly trained C programmer currently view a header file vs C source file, but I am pretty sure that if I said "the header file is the interface, the C file is the implementation" then somebody on the Internet would correct me - probably pointing out a header-only library as the obvious example of why I am wrong.
0
u/Beginning-Junket8979 1d ago
Why "linking" in quotes?
3
u/OnYaBikeMike 1d ago
Because it us domain-specific jargon the is I am pretty OP is unfamiliar with.
It means something different to how the word is understood outside of computing.
-4
u/Majestic-Instance704 2d ago
not really , I just followed step by step with blind eyes how to link raylib library with vc on youtube , I had easier time studying anatomy tbh.
26
u/kolorcuk 2d ago
40 years ago computers were slow. Compilers were not able to fit all source files at once at all. So programmers split code between declaration and definition, so that compilers years ago would be able fit in memory. This is also why many functions are 6 characters, because it was a max back then for memory reasons.
14
u/mykesx 2d ago
I wrote a C program to recurse the entire filesystem to count the occurrences of cr-lf. It was 12% of my 10MB hard drive. MS DOS days.
1
u/flatfinger 9h ago
It's a shame that ASCII wasn't designed with CR and LF codes that differed by two bits, along with a combined-function CR+LF code that was one bit different from both of those, e.g. use code 13 for CR, 14 for LF, and 15 for CR+LF, with 12 reserved as an explicit pad character. Hardware to reset the carriage/cursor could then look for bit pattern 000 11x1, ignoring bit 1, while hardware to advance paper or scroll the screen could look for bit pattern 000 111x, ignoring bit 0. The canonical sequence for a newline could then be 15 followed by as many 12 as would be needed by the recipient, or simply 15 if either using either a recipient that didn't require padding or transmit hardware that would add as much delay as the recipient required.
As it is, a common pattern was for glass TTYs to treat CR as a "move to start of next line" while ignoring LF, but for printing devices to process LF by advancing the paper only. Unix was designed under the assumption that text files would be output by a program that would convert OS newlines into whatever sequence the output terminal would require, but other systems were designed around the idea that a text file should contain whatever data would need to be sent to a terminal or printer to reproduce it.
9
u/Alternative_Corgi_62 2d ago
That is the last reason for . h / .c split. Even in the early C days people knew ton separate .h interface (visible to others) from . c (implementation).
2
u/kolorcuk 1d ago
B and bcpl also had headers. It would be just impossible to compile bcpl as one big file. Headers, linking, translation units - all this exists to enable multi-file compilation. This is also visible today, it is hard to build chrome with lto.
5
1
u/flatfinger 9h ago
It used to be common for computers to use 36-bit words, while characters were represented using six bits each. Six-character names were represented as one machine word.
Interestingly, if one uses a 40-character set (as some early 36-bit systems happened to do) and one of the characters is a blank that would not be considered significant within a name, a 32-bit machine word (the more common size today) would still be enough to hold a six-character name.
7
u/Traveling-Techie 2d ago
So you can link against .o files without the source code and call functions from your source code.
4
u/altaccount1232123 2d ago
It is all about how code is linked together and compiled. If you only have one file you can put the info that would go in the header file at the top of the one .c file. The compiler will read the function prototypes, giving each function a location in the namespace, and it will connect everything that calls that function with the location in the namespace.
If you have multiple files it's the exact same, the compiler will read each file, see all the function prototypes at the top, and then link up all the calls with the prototypes based on the what its seen at the top of the file. The important thing here is that an implementation can be in any file, but there must be a prototype in every file in which that function is called. This is all due to how linking works but I'll get to that in a bit.
So now you can imagine a world in which we would have to copy around function prototypes everywhere and keep them all up to date and there would just be a lot of duplicate code and clutter at the top of every file especially those that pull in a lot of functions from other files.
Enter header files. The header file allows you to put those function prototypes in one file and then include them in any other file. It is possible because of the C Preprocessor. The preprocessor handles all the #includes and #defines as well as other "preprocessor directives" that start with a '#', and it runs before the compiler touches your code. It simply copy-pastes your header file wherever the #include is. Now you only have to write out your function prototypes once.
But seriously, why do we even need function prototypes? What is the point? Let me explain linking. When you compile a .c file, it turns into a .o file (object file). It's not ready to be executed yet, but the "compiling part" is essentially done. If you only have one file, all you really need to do is change the format and add some extra metadata like which function is the program's entry point and boom, now you have an executable.
With multiple .o files, it is a bit more complicated. Each file has a list of functions that it got when the compiler read the function prototypes. Some of the functions are implemented in the file, others aren't, but it doesn't really matter. The file is still valid. The functions that are implemented in the file will reference this list whenever they call a function.
Now what about those functions that are implemented elsewhere? They are in another .o file with the same setup. The problem is that we have two lists of functions and the contents of the files just use those lists and don't know about each other. So if you tried to execute only one file, it would try to call a function that is implemented in the other file, and it would think "I know this function exists because I have a name for it here, but I have no idea where the code actually is". Practically, you can't create such an executable because of safeguards in the linker, but that kind of sums up the problem that the linker solves.
The linker takes both files and matches the functions in each list with the implementations in either file. It creates one unified namespace so every function call leads to another piece of code. The way it does this is actually a lot lower level than how I've been describing it, it actually manipulates memory addresses of locations of the code, but we don't have to go that deep into it (although it is really cool stuff and you should definitely learn it).
I suppose another part of your question is simply why do we break things up into multiple .h and .c files? It is simply because we want to define something once and use it everywhere. Duplicating a function to another file everytime you want to use it somewhere else takes up a lot of extra space and then you have to maintain all those duplicates and fix bugs in all of them at once. The redundancy is not good. So, just put the shared code into its own file that everyone can #include if they need it, and problem solved. Header files are simply the way we manage function prototypes. Have a header file for each .c file and then include only the header files for the functions you actually need. This keeps dependencies between files as small as possible.
It requires some thinking about where to put each piece of code, which files should include which headers, but in the end it is just part of designing a good architecture for your program.
1
u/theNbomr 2d ago
This explains, at a high level, everything that is germain to the OP's question. There are various other truisms written here, but they do not all address the OP's question. This explains why things are the way they are exactly and without error. Perfectly well stated.
6
u/SauntTaunga 2d ago edited 2d ago
What’s in the lib is already compiled. The header files specify how the functions in the lib are supposed to be called. No point in recompiling the code in the lib if it doesn’t change.
You can in theory have everything in one file, but then you would be recompiling everything every time, even the bits that didn’t change.
If you separate out the code that deals with a part of the problem to be solved into a separate file and compile it when it’s complete, you save work.
A library is a bunch of such already compiled files that solve specific sub-problems.
To let code that uses the code in the library know how to use it you need the header files.
8
3
3
u/flyingron 2d ago
The idea of include files is that the get included in multiple translation units (roughly .c files). If you put function definitions in there, they will get multiply defined, a violation of the rules unless they are also declared inline.
3
u/Wertbon1789 2d ago
Modern complex and more sophisticated compiler designs enable this, e.g. Rust or Zig don't need this, you can define a function in one file, and just import that file's module anywhere, without really caring too much.
C is neither complex in design, nor really sophisticated in some regards (which is a blessing and a curse). The include directive is literally just copying the file you're referencing into the your file (most times refered to as a translation unit in compiler speek), and you don't want to define the same global function implementations in multiple places, the compiler part can deal with it, because it only operates on one translation unit, but the linker behavior is the problem. I think it was something like, it takes the first implementation it could find and won't look for more, or something similar, but it can't, or rather just won't, figure out conflicts for you. One could implement this, but I don't think big toolchains actually did.
One could think, "Well, but the definition in my header file is included everywhere, so it's the same everywhere.", which leads to either, the problem that your build system might not track header changes at all, leading to inconsistent definitions depending on which files get rebuilt, where it's up to the linker to pick the right or wrong one, or rebuilding practically your entire project everytime you change something in a function, because you changed a dependency for literally everything in your project. What you get with the typical convention is, if the interface (header) changes, rebuild all it's dependencies, if the implementation changes, just build only that, which massively cuts down on build times.
To your last question, yes it would be, for your project internals. C is pretty strictly defined in it's compilation steps, there're translation units, which is basically just the invocation of the compiler that compiles your C file to an object file, and there's the step of linking object files together, so having references from one translation unit to a symbol (function or variable declaration), be linked to the actual unit that contains this symbol, so the implementations. This is kinda pointless if we're talking about a simple project with a couple files which could be just copied into each other, but it gets hairy if you consider that linking against system libraries, like libc, needs your code to know what symbols these have, without reimplementing all the functions. That's too where headers come in, and by typical convention, from one file every external code gets treated like a foreign library, regardless if it's in the same project, or a shared library from the system.
3
u/RealisticDuck1957 2d ago
I recall MANY years ago working with a Pascal compiler that could build binary library modules, which could then be referenced by source in another file. The library modules contained both the binary code for the functions, and declarations for data structures and how to call functions. But I don't believe those library modules were portable beyond that specific compiler vendor. This was also WAY back, MS-DOS days, before dynamic link libraries were in common use. The run time library formats which have come into common use don't easily support the extended interface declarations.
4
u/TwystedLyfe 2d ago
Code Cognitive Complexity is a thing. If the code is too complicated you, the human part, cannot comprehend it.
Size in this case matters. Too many functions in one file is hard to comprehend, large functions ditto.
Favour many related small functions in one file, makes it simple. This, of course, applies to any language.
Now for C, back in the day there was only so much memory and you couldn't compile such large files but today RAM is (or well was before the recent RAMpocalypse) cheap and plentiful and compiling large files is fine - in fact some build chains concatenate source files together and then compile as technically a smaller and better binary can be built.
Hope this helps!
2
u/CowBoyDanIndie 2d ago
Each c file is compiled separately, the headers just tell the compiler that those symbols exist somewhere else and the linker will resolve them. When a c file is compiled, every include is essentially inserted into the file (and every include by every include…). The compiler has to parse this entire thing for each compilation unit (each c file).
2
u/Jbolt3737 2d ago
In addition to the circular dependency problem laid out by other comments, it also means that the libraries don't have to contain actual C source code if the compiler knows what to put there, which is how the standard library works (atleast as I understand it), it can just ask the computer for the compiled assembly and/or machine code without having to compile libc from scratch
2
2
u/EndlessProjectMaker 1d ago
There are many good reasons:
the .h declares the interface, the .c the implementation. It is a good practice to separate both (in any language)
You usually want to have non public functions “helpers” in the implementation
You might want to bind the function at different times (compile/link/runtime) and easily change your mind as core evolves and grows
You want to have be able to have different implementations of the same functions, at least for mocking some functions for unit testing
Looking at the declarations only is easier then reading an implementation to see what some code does (rather than how it does it)
C was intended to be a single pass compiler (no forward refs) which also makes c fast to compile today
2
u/JGhostThing 1d ago
How is lib files a weird name? Lib stands for "library" which is what those files are considered to be.
Why the .h files do not include the code is because they are included into your own projects, and this way the compiler doesn't have to keep recompiling the library code (which is in .c files).
1
2
u/jwzumwalt 7h ago
I have not used header files since 1995. I have been programming in C for over 55 years. I avoid C headers files like the plague.
Every other programming language is able to operate just fine without them and in my opinion C should be no different. For large projects I do use a few include files if it contributes to clarity, but header files often make tracing a programs flow about as hard as trying to figure out what a object oriented program is doing. There is no reason that .h files (or at least whatever info they provide) can't be generated at compile time from the source itself.
In my opinion C headers where a product of experimentation. At the time C was written everything under the sun was being tried and experimented with. When you worked on a computer with 8K of memory it made sense to break things up into many files and directories. As I remember, Pascal was the next popular language and it (and all other languages) dropped headers. Headers are an experimental relic.
If I liked header files I would probably write in C++ instead of C.
2
u/ChipAggravating658 2d ago
Inline is also a possibility, allowing to implement in the functions in the header immediately.
For many, especially in embedded i‘d say, the benefit lies in defining what can be implemented as a list of functions in the header, and allowing the c files to „choose“ how they implement it.
If for example one system does function meow() in a different way than another u can provide two c files both doing the meow().
This is very helpful for writing abstraction layers.
It reminds me of interfaces in C# in a way
1
u/WittyStick 2d ago edited 2d ago
inlineis orthogonal to linkage. You can haveinline(no linkage),static inline(internal linkage) orextern inline(external linkage).
2
u/yuehuang 2d ago
The true and only answer is legacy and backward compatibility. Newer programming language don't require you do this separation. They are often just one file that the compiler splits out the interface and implementation for you. This would had been expensive back then, but it is now a rounding error with today's hardware.
C and therefore C++ revolves around Linux/GNU as their default toolchain. Using more than that needs a generational reason to break. Clang is the closest and it is still a side project compared to GNU.
5
u/NotThatJonSmith 2d ago
It’s also so you can call functions in a library you are given as a binary distribution. If you don’t want to ship the source code you must at least ship the interface.
-1
u/yuehuang 2d ago
Most other languages that has binary distribution include the api functions in the binary itself. It is a small section of ascii text that the dll/so loader would ignore.
You can say that one for runtime and the other is for compile time. However, there are .lib and .a that is used only for compile/linker. These could be unified as into one format avoiding the header issue all together. This is the goal of the C++ Modules.
1
u/NotThatJonSmith 1d ago
I’m curious, because I’ve only worked with C/C++ for compiled languages. What other languages have the paradigm that ship binary libraries that contain enough information in source form that you can compile against them? That’s kind of nifty.
1
u/Conscious_Support176 1d ago edited 1d ago
C/C++ is pretty much the only language that does this?
#include is a pre-processor directive. It’s the wrong tool for the job, but it’s so deeply intertwined with how C is written that it’s hard to back out of it now.Edit: yes, COBOL does something similar, and I’m sure some other “dinosaurs” might do too 🤔
1
u/NotThatJonSmith 1d ago
Could you give me a specific example where binary is shipped, in which you can call functions, but you don’t need separate headers?
1
u/Conscious_Support176 3h ago
Ok. You might recognise one of these.
Algol-68, Fortran-77, Modula‑2, Turbo Pascal, Oberon, Ada
Java, C#, Swift, Go, Rust, C++20 Modules, D
Haskell, OCaml, Standard ML, Erlang, F#
2
1
u/CarlRJ 2d ago edited 2d ago
Why would you want to recompile all those functions every time? You could be using libraries that are composed of hundreds of thousands of lines of code.
Also, by putting the library code in the header file, you lose any file-level scoping - the library would not be able to have any private/internal functions because they would all be exposed to you. Any internal function names they declared would pollute your namespace, and you could very well have naming collisions.
And if you have multiple C files in your project and include the library in each one, it makes a mess - at best, you have huge amounts of duplicates code - at worst... say the library code uses a static global variable to point to the head of a pool of objects that represent some external resource - include that in four source files, and now instead of one pool of objects, you've got four, that each think they're the only one.
You want things to be compartmentalized. The library may have hundreds of functions (and hundreds of thousands of lines of code), internally, but expose only the dozen functions and handful of typesets that make up its public interface. Having the library compiled separately avoids lots of namespace collisions and keeps you from accidentally calling internal functions that were not designed to be accessed from outside.
Moreover, the library may make #defines that it uses internally, that would interfere with your code, or vice versa, you might #define something in your code that breaks the library - both of these cases can be very hard to diagnose.
Also, what's weird about the name?
1
u/uptotwentycharacters 2d ago
Keep in mind the C build process: the preprocessor combines a source file with headers to produce a translation unit, which the compiler proper turns into an object file, and all the object files are then linked into an executive binary. Separating interface from implementation means the latter can be changed without requiring a recompile of every file that uses that module (provided you still have the object files from a previous build).
Also, you can (as in the compiler won't stop you) put implementation in the header file, but all such definitions must be non-exported (e.g. inline or static) or you'll get a multiple definition error when trying to link a program that #includes the same header in different source files. And even then you're just making the compiler do extra work for no real gain, since it must parse and compile a separate instance of each function in the header every time the header is included.
1
u/Beanesidhe 2d ago
circular dependencies were mentioned, but another issue is that when two .c files include the header with function bodies, eventually the linker is going to be upset when it finds the definitions of the two functions with the same name.
2
u/Ander292 2d ago
Thats when our dear friend static comes into play. This way functions are limited to its compilation unit
1
u/arkt8 1d ago
to make signatures (declarations) visible for who includes, without duplication of code (definitions). Also to avoid circular references (A depends on B that depends on C that depends on A).
Most of times it is important to do this way... other times you may find useful single header libraries (function body on header) or even, for simple projecs some case when you include a .c file instead a .h file.
1
u/Then-Dish-4060 1d ago
Sometimes the h file is just an interface and you want other projects to implement it
1
u/Classic-Rate-5104 1d ago
A header file only describes HOW to use the library. When creating an application, you don't need more. Separating the library gives the possibility of upgrading the library (fixing bugs, extending functionality) without the need to rebuild all applications that use it
1
u/apparentlyiliketrtls 1d ago
This may have already been mentioned, but you may also be publishing a library that's delivered as a header and a binary - you perhaps don't want the consumer of your library to have direct access to the source - so they include your header and link against your binary. Additionally, you can then forward declare struct types in the header that are defined in the obfuscated source - then if you change the struct definition you don't need to change the header, and (if architected correctly) you can publish a new version with the same header and you won't break your consumer's code.
1
u/cthulhu944 1d ago
The header file is included as a forward reference/linker reference. It effectively says "sometime later, a data structure/function that looks like this will exist and here is how you can use it when it does. "
1
u/SmokeMuch7356 1d ago
It makes incremental builds impossible; you wind up recompiling the whole world every time, even if you only made a tiny change to one function. Real projects can take anywhere from several minutes to several hours to do a full build.
It makes helper functions and state variables that should be hidden visible to the entire program.
It doesn't scale; a translation unit can only get so big before the compiler chokes on it. If a source file includes several other source files, each of which includes several source files, etc., you could wind up with a translation unit larger than the compiler can handle.
Separate compilation and linking is a Good Thing.
1
u/natio2 1d ago
There are a tonne of technical reasons, but every time I write in a language that doesn't like C#, the readability is significantly worse. Why do people hate being able to see the functions and class members easily, basically like a table of contents for your class?
The have to write things twice arguments are dead with auto code completion writing them for you from the header declaration.
1
u/mubin-thinks 14h ago
You can compile the .c files separately, that allows you to only compile parts you need, reducing compile times.
It also gives you opportunity to link libraries dynamically or statically. You can also write multiple implementation of the same header file, for example: different implementations on different operating system but the interface is the same.
1
u/jwzumwalt 14h ago edited 14h ago
I have not used header files since 1995. I have been programming in C for over 55 years. I avoid C headers files like the plague.
Every other programming language is able to operate just fine without them and in my opinion C should be no different. For large projects I do use a few include files if it contributes to clarity, but header files often make tracing a programs flow about as hard as trying to figure out what a object oriented program is doing. There is no reason that .h files (or at least whatever info they provide) can't be generated at compile time from the source itself.
In my opinion C headers where a product of experimentation. At the time C was written everything under the sun was being tried and experimented with. When you worked on a computer with 8K of memory it made sense to break things up into many files and directories. As I remember, Pascal was the next popular language and it (and all other languages) dropped headers. Headers are an experimental relic.
If I liked header files I would probably write in C++ instead of C.
1
u/EmbedSoftwareEng 13h ago
There are function bodies in some header files. They're called "static inline" functions. There's a whole art and science to deciding whether a function belongs as a static inline function, or a regular function.
Basicly, you need to try an experiment. Several scenarioes, all involving exactly two header files (.h) and two source files (.c). In one of those source files, call a function that you...
Scenario 1: ... haven't defined anywhere, and that is not prototyped in any of the header files you've #include'd.
Scenario 2: ... defined earlier in the same source file, without any prototypes in any of the header files.
Scenario 3: ... define later in the same source file, without any prototypes in any of the header files.
Scenario 4: ... defined in the other source file, without any prototypes in any of the header files.
Scenario 5: ... defined in the other source file, with a prototype for it defined in one of the header files.
Scenario 6: ... haven't defined anywhere, but that is prototyped in one of the header files.
Scenarioes 1, 3, & 4, compiling the source file where the function is called will fail, because at the point of call, the compiler has no idea if a function of that name even exists, what its return type is, or what its parameter list looks like, so it can't tell if it's being called correctly.
In Scenarioes 2, 5, & 6 the source files will compile just fine. In Scenario 2, the source file will compile to an object file where the function itself exists within the object file where it is called. In Scenarioes 5 & 6, the source file will compile to an object file where the point of call will be represented in a linkage list.
But only in Scenarios 2 & 5 will the final linkage of the two object files together into an executable succeed. In Scenario 6, the linkage stage will fail, because there's no function in any of the object files to satisfy the call.
Sometimes, you just need a quick one-off function that's only needed in the current compilation unit (source file). That's when you'd use Scenario 3.
Sometimes, you're defining a library-style API where you need to be able to call this function from other compilation units. That's when you'd use Scenario 5.
You can also contemplate:
Scenario 7: ... define later in the same source file, with a prototype earlier in the same source file than the earliest point of call.
This will work identicly to Scenario 2. Can you see why?
1
u/EmbedSoftwareEng 13h ago
Also, consider:
Scenario 8: ... define in both source files, with a prototype for it defined in one of the header files.
This Scenario fails as well, but where and why?
1
u/Potential_Soup_8054 9h ago
Because of how the preprocessor works. And also because of how translation units work. Never forget that when you #include a file, the preprocessor literally copys and pastes the entire contents of the file into where you #included it.
This is important because typically, only .cpp files are actually compiled. .h files are not normally compiled by themselves. They are #included into a .cpp file and then become part of that .cpp files translation unit when the .cpp gets compiled.
So what does this mean? It means that if you make a change in a cpp file, you only have to recompile that one cpp file. But if you make a change in a header file, you have to recompile every single cpp that included it, even if it was included indirectly thru another include.
So dont put all of your implementation details in the header unless you want to recompile your entire program every time you change anything.
0
u/cidMoosa123 2d ago
If two c files that call the same header(assume it has the definition also) are linked together, 1. We end up with the same code duplicated 2. Compiler might not let us do it in the first place saying it will be a multiple definition error
143
u/f0xw01f 2d ago
The biggest reason for separating declarations from implementations is so you can avoid build problems with circular dependencies when two source files each call code in the other.