Products > Programming

nesting of .h files and .h files which start with an underscore

<< < (4/6) > >>

ejeffrey:

--- Quote from: peter-h on October 12, 2021, 02:51:40 pm ---"so #include <time.h> must appear in “KDE_NTP.h”"

Are you saying that nested .h files is the right way?

--- End quote ---

Yes, absolutely.


--- Quote ---I would have thought that for long term maintainability each .c file should #include the .h files it actually needs, only.

--- End quote ---

Every *file* (including headers) should include exactly the .h files it actually needs.


--- Quote ---The problem is that a lot of the "standard" .h files (like time.h) contain loads of #includes, among a mass of conditionals.

--- End quote ---

If the C, POSIX, or other relevant standard says you need to include time.h to bring in a certain definition, that is what you do.  You should *never* #include any libc internal headers directly.  You shouldn't even have to look at them although at times it can be helpful.


--- Quote ---
--- Quote ---"The IDE is cheating, it parses all the files and follows all possible references."

--- End quote ---
That explains a lot.

--- End quote ---

IDE editors have the unenviable job of doing 99% of their work with incomplete or incorrect code.  So their syntax highlighting and definition lookup needs to work even in the case where your code wouldn't compile.  It's parsing and lookup functions are therefore quite different than a compiler.

ejeffrey:

--- Quote from: peter-h on October 12, 2021, 06:04:02 pm ---Another .c file may need the kde_ntp.h but if it doesn't need time.h, why supply it?

--- End quote ---

If the .c file needs kde_ntp.h, then it *does* indirectly need time.h -- as in, it won't be able to use the definitions from kde_ntp.h if time_t isn't defined.

The difficulty here is that if some other file bob.c #includes kde_ntp.h but doesn't realize that imports time.h, bob may use names that conflict with unrelated definitions in time.h.  This is just a hazard of using C. This is the reason why C++ has namespace support and most other languages that don't try to be C compatible eliminate header files and include directives altogether in favor of some form of packages + import/use/require directives.

peter-h:
"Every *file* (including headers) should include exactly the .h files it actually needs."

Maybe I didn't explain it well but I think I can see the answer (there isn't one).

If you have fred.h and in it you include time.h, and you have two .c files, both need fred.h but only one needs time.h, then it seems dumb to include time.h in fred.h. IMHO time.h should be included separately in the .c file which needs it. Otherwise, every .c file which is getting fred.h will also be getting time.h and you could get a name conflict with some function you have written, etc.

Obviously if fred.h itself needs time.h then you have to include tim.h in fred.h. That happens sometimes, but from what I have seen most nested includes are accidental, and since most coders are just trying to get a product to work, if it works, you leave it.

"IDE editors have the unenviable job of doing 99% of their work with incomplete or incorrect code.  So their syntax highlighting and definition lookup needs to work even in the case where your code wouldn't compile.  It's parsing and lookup functions are therefore quite different than a compiler."

It would still be handy if there was some way to see how the IDE is determining the location of the declaration. I have some variable declared in loads of .c files and somehow the IDE is picking the one it thinks is the right one. Presumably it scans the .c file itself first, including all #included .h files. But that's what the compiler should also be doing. It's probably quite complicated.

newbrain:

--- Quote from: peter-h on October 12, 2021, 02:51:40 pm ---"so #include <time.h> must appear in “KDE_NTP.h”"

Are you saying that nested .h files is the right way?

"Single underscore is library-internal, double underscore is compiler-internal, I believe"
[...]
That is how it appears, which is why I resisted doing a #include with a .h file which starts with one of these. Wasn't sure why though.

"A number of variables were shared between modules, and I had to declare them extern to get correct function without errors."

Yes; what I found works is e.g.

uint32_t g_fred=0;

in one file (perhaps in main.c or whatever module starts running first) and then in each of the other files you do

extern uint32 g_fred;

Cube is Eclipse based so lots of people should know it.

--- End quote ---
Some random thoughts and references:
Nested includes:
Yes, that is the recommended way to do it. Each include file should be self consistent and include all the headers it needs.
The .c file should also include all the header it needs, and not rely on a header to implicitly include something it needs: as Siwastaja said, compilers are efficient enough (some cache the headers and optimize the standard #ifdef guard, so one deos not need to use the non-standard, but very widely supported, #pragma once)
The recommendation can be found in e.g. Google styleguide for C++ (no difference with C in case), see also the following rules for declarations (more on that below).

Identifiers (and other) with leading underscore(s):
The C standard, in chapter 7.1.3 (C2011) makes a subtle difference between a leading underscore followed by an uppercase letter or another underscore, and a leading underscore followed by anything. In the first case the identifier is reserved for any use (e.g. also extra keyword, like __always_inline), in the second it's reserved for use as a file scope identifier and also for tags.
The point is so fine that the rule here is simply DO NOT USE a leading underscore and you'll be fine.

extern declarations and definitions
The rules are outlined in chapter "6.9 External Definitions".
They boil down to what you observed: for any object that is actually used there must be a single definition (i.e. a declaration that reserves the object space) and there can be many external declaration (simplifying, just letting the compiler know the type of the identifier).
uint32_t fred = 0; is a definition as it includes an initializer.
uint32_t fred; would have worked the same, in that case it's called a tentative definition, and there can be more than one as long as they agree: if no definition is found the compiler will take it.
Note that this applies regardless if the linkage (=visibility of the identifier) is external or internal (e.g. the definition uses "static", though a declaration in the same file uses "extern"), see the examples at the end of chapter 6.9.3.

Cube
All of the above has no relationship whatsoever with Cube.
The compiler enforces the standard (possibly with some extension), the header files (real or "virtual" - they need not exist physically, though they
mostly do) are provided part by the compiler and part by the used standard library.
The source parsing function, as many said above, can throw you off - that happens quite rarely to me with either internal or clang based Visual Studio Intellisense, I don't know what Eclipse 🤮uses.

newbrain:

--- Quote from: peter-h on October 13, 2021, 08:03:47 am ---"Every *file* (including headers) should include exactly the .h files it actually needs."

Maybe I didn't explain it well but I think I can see the answer (there isn't one).

If you have fred.h and in it you include time.h, and you have two .c files, both need fred.h but only one needs time.h, then it seems dumb to include time.h in fred.h. IMHO time.h should be included separately in the .c file which needs it. Otherwise, every .c file which is getting fred.h will also be getting time.h and you could get a name conflict with some function you have written, etc.

Obviously if fred.h itself needs time.h then you have to include tim.h in fred.h. That happens sometimes, but from what I have seen most nested includes are accidental, and since most coders are just trying to get a product to work, if it works, you leave it.

"IDE editors have the unenviable job of doing 99% of their work with incomplete or incorrect code.  So their syntax highlighting and definition lookup needs to work even in the case where your code wouldn't compile.  It's parsing and lookup functions are therefore quite different than a compiler."

It would still be handy if there was some way to see how the IDE is determining the location of the declaration. I have some variable declared in loads of .c files and somehow the IDE is picking the one it thinks is the right one. Presumably it scans the .c file itself first, including all #included .h files. But that's what the compiler should also be doing. It's probably quite complicated.

--- End quote ---
I think you got it right(Edit: no you in fact did not, rereading yours and Golden Labels posts) - if fred.h uses time.h it should include it. If bob.c needs fred.h it will include it - but it should not rely on time.h being included: if something from time.h is needed in bob.c, bob.c should explicitly include it.
In this way, if fred.h implementation changes so that it does not use time.h any longer, nothing will break.

Good IDE either automatically detect the include paths (along with e.g. compiler defines) used by your build system, or allow you to define them manually.
If you follow some good rules, you won't have the same variable declared in a number of different places: you should have a single definition and a declaration in an include file to be used by whichever translation units need to access it (see the Google style guide I linked above).
Sprinkling "extern" declarations in .c files is often a recipe for unmaintainable and easily broken code.

Navigation

[0] Message Index

[#] Next page

[*] Previous page

There was an error while thanking
Thanking...
Go to full version