[C] do i need a *.h file for every *.c file? - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: January 21, 2019, 09:51:27 am
content: New project new questions.So obviously I will split my project into several c files but do I really need a header file for each? If i list the various function prototypes in main.h will that suffice?I'd also like to put all variables (they are mostly all external) into a separate c file, again is this OK ? or is a header file best?In the past I had tried to have my own "library files" but i have abandoned that idea in favour of writing my own instruction manual with gotcha's and all for each module and usage case of the micro controller used so that i just run through those instructions and set up the peripheral from scratch in each program so no file will necessarily be reusable in another project.

author: Jeroen3
author_rank: Super Contributor
timestamp: January 21, 2019, 09:55:09 am
content: No. But you might want to manage dependencies, and which modules recompile when changing something.With one header file, all C files recompile.

author: Rerouter
author_rank: Super Contributor
timestamp: January 21, 2019, 09:56:19 am
content: If its for yourself, the sky is green and the ocean is orange, anything goes as long as it does what you need it to, and when you come back to it you can pick it up without requiring twilight madness/clarity for it to make sense.If your making code for a job or others, probably find a style guide and refer to that. e.g.https://google.github.io/styleguide/cppguide.html

author: Simon
author_rank: Unknown
timestamp: January 21, 2019, 10:00:51 am
content: Well i am trying to make it simpler to go back to. With every additional file actually being 2 additional files the complexity goes up fast. Ultimately it is a program that will be split into separate c files for ease of managing and clarity. most variables will be global anyway.Do i assume correctly that a variable declared outside of a function in any file will essentially be taken as declared globally before any of the c code runs? I guess i will find out soon

author: Jeroen3
author_rank: Super Contributor
timestamp: January 21, 2019, 10:08:25 am
content: Everything in one big file makes it more obfuscated.Quote from: Simon on January 21, 2019, 10:00:51 amYes. But there is thestatickeyword to keep the variable within the file.

author: Mechatrommer
author_rank: Super Contributor
timestamp: January 21, 2019, 10:34:19 am
content: Quote from: Simon on January 21, 2019, 10:00:51 amthere are few reason why you need *.h file...1) a place to declare function's prototypes for usage in the same c file.2) to publish "public" functions/classes/types etc for usage from different c files (some call modules some call units)3) to gather few *.h files in one single h file for easy structure call (include)4) others may add...if non of the above that you need, your c doesnt need h file.there are few things *.h file is not for...1) declaring (with definition) variables that will allocate space in memory. allocation/definition should be made in c file2) class with template (undefined data type T)3) others may add...<give a man a fish he will feed for a day. teach a man how to fish he will feed for a lifetime>Quote from: Rerouter on January 21, 2019, 09:56:19 amthis quote is good for stubborn man. but soon he will realize that green and orange will bite on his butt after a while if he doesnt get it right. there are reasons why experienced professionals laid out things in a way we thought unreasonable...

author: Simon
author_rank: Unknown
timestamp: January 21, 2019, 10:44:37 am
content: I am just trying to avoid doubling my files and making it as unmanageable as having one file.

author: CJay
author_rank: Super Contributor
timestamp: January 21, 2019, 11:27:46 am
content: Quote from: Simon on January 21, 2019, 09:51:27 amAs a 'tinkerer' programmer, if I write a module to control, say, a DDS chip or RTC then I create a .h to go with it.To my mind, it makes it easier to re-use the code, I'm sure other people will disagree but it works for me and that's what's important.

author: Simon
author_rank: Unknown
timestamp: January 21, 2019, 11:33:10 am
content: a function produces a value of a tacho signal frequency, that tacho value is used by a speed setting function to correct a speed. tacho is therefor a global variable as two functions have access to it.the program runs on a timer that carries out the main function every "x" time and that value is used by multiple functions, it is therefore a global variable.buttons will be pressed and every time a button is scanned by the button read function the "button pressed" variable will be updated and then looked at again when that function runs again so "button pressed" is a global variable or it's value will be lost at the end of the button scan function.Considering it is not a massive program and that most variables need accessing by consecutive instances of the same function without values being lost or variables are used by multiple functions all variables will end up being globals and this also makes me disciplined in naming variables

author: taydin
author_rank: Frequent Contributor
timestamp: January 21, 2019, 11:39:42 am
content: There are two main purposes for header files:1) Defining global values that are used throughout the project. For example, a network port address, maximum minimum values for certain settings, structures, type definitions that are used througout the system. Header files avoid duplication by allowing you to just include the header file and use the contents whereever you want. And when you change one of the definitions or values in the header file, it is reflected throughout the entire project. This is a very powerful tool to avoid duplication.2) Making the functionality implemented in a C/C++ module available to other modules. You add all functions, structures, typedefs and defines in the module that are for public use into the header file, and then whoever wants to utilize the capabilities in the module just include the header file.There are other advantages and valid use cases as well, but the above are the ones that have the most impact.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: January 21, 2019, 12:10:33 pm
content: Another approach is to have a .h file for different logical categories of information, e.g. one for hardware port definitions, one for global constants, one for compile-time configurations, one for protocol-related parameters, and each C file includes only whichever ones are relevant to it.I think that can make more logical sense than a .h for each .c file - I don't really see much point in the latter approach.

author: amyk
author_rank: Super Contributor
timestamp: January 21, 2019, 12:46:18 pm
content: It doesn't matter to the compiler so there is really no right answer --- do whatever feels right and most convenient to you.

author: GeorgeOfTheJungle
author_rank: Super Contributor
timestamp: January 21, 2019, 12:56:09 pm
content: Keep in mind that you can include a .c into a .c simply #include "pwm.c" or #include "globals.c" or #include "buttonscode.c" etc. do that wisely and you'll need no .h headers nowhere.

author: Simon
author_rank: Unknown
timestamp: January 21, 2019, 01:08:38 pm
content: Quote from: GeorgeOfTheJungle on January 21, 2019, 12:56:09 pmThat sounds much easier although if you are talking make file stuff the IDE handles that although yes if I don't declare functions before they are called there will be an error and putting functions in other non main.c files seems to amount to appending those files after the main function. I assume the preprocessor runs through all files and picks out any global variables declared. So including a c file would make life much easier.

author: rhb
author_rank: Super Contributor
timestamp: January 21, 2019, 01:12:57 pm
content: My basic practice:Header contains anything that the caller needs to know to use the module and nothing more.Any globals shared by functions in the module are declared "static" to restrict them to file scope.If there is information (e.g. array sizes) which multiple modules require that goes in a separate file.If using function putfoo() requires using function getfoo() both belong in the same module. Anything that might be used without ever using another function belongs in a separate module.The reasoning is as follows:Every piece of information should exist in one place and one place only. This prevents making changes which are incompatible and will cause an error.The linker includes everything contained in a file whether it is used or not. So if all the functions are in a single file, all of the functions are incorporated in the executable and loaded into memory if a single function is referenced. (NB: I have seen claims that this is not true with the Gnu linker. I don't know. But it has been the case on over a dozen Unix systems I've worked on)If you have multiple related functions which may be used independently of each other, rather than a header for each module you put the modules in a library and have a single library header file. If several functions need to share a global variable, those go in a module with its own header which includes the library header.You should only use global variables if it makes the code more readable or if the object is too large to be allocated on the stack as an automatic variable (i.e. large arrays). If you use a global,restrict the scope of the variable as tightly as possible using "static".Remember, you are writing something which will need to be read at some time in the future. You want to communicate as clearly as possible to the reader.I rarely use globals unless forced to by the limitations on the size of automatic arrays. However, I once wrote a parser for an ad hoc data format that what complex. The resulting code was a loop with a long If-elseif block. Each conditional contained a function call. As I was finishing it up I observed that of a dozen or so functions, all of which took the same 4 parameters, two of them had 6 parameters. So I made the 4 shared parameters file scope globals so that anyone reading the code in the future would immediately see those two functions were different.So I changed from:if( foo_1(a,b,c,d) ){}elseif( foo_2(a,b,c,d){}elseif( foo_3(a,b,c,d){}elseif( foo_4(a,b,c,d){}elseif( foo_5(a,b,c,d,e){}toif( foo_1() ){}elseif( foo_2(){}elseif( foo_3(){}elseif( foo_4(){}elseif( foo_5(e){}If you have a number of related functions which can all be used without using any of the others, put each function in its own .c file and put all the function prototypes and other information needed by the caller such as sizes in a library .h file. If some group of modules need to share information that the other modules don't need, create a separate header for those.In summary, a large library might have several dozen .c files, a library .h file and a couple of .h files only included by a few files.You should get a copy of the C standard if you don't already have one and read it any time you are doing anything you haven't been doing all day. Study the varied effects of "static" at file and function scope. Pay close attention to anything that is "undefined" or "implementation defined" and avoid doing the former at all times and the latter unless you absolutely have to, in which case guard it with a #ifdef _foo_system_ so that it will have to be looked at to make it compile on a different system.As for the comments made while I was writing this. Having had to fix a few million lines of code written with that mindset, *please* don't do that. It causes all sorts of headaches. If you program well, you write the code and then use it for years without ever making any changes. Having written a pair of 15,000 line libraries which continued in service for over 15 years with only one change and *no* bugs reported I know ti can be done. The one change was in a function that did the following:errno = 0;str = getcwd( str ,len(str) );if( ernno){fprintf( stderr, "getcwd() failed/n" );}An OS update by Sun resulted in errno being set non-zero even though the call had succeeded. The friend who was still maintaining the code changed it to test if a null was returned. He told me about it over a lunch several years after I wrote the code.

author: tggzzz
author_rank: Super Contributor
timestamp: January 21, 2019, 01:17:59 pm
content: Quote from: Simon on January 21, 2019, 09:51:27 amAbsolutely not.It is normal to have header fileswithoutcorresponding c files. A classic embedded example is a list of i/o ports.It is normal to have one header file and multiple c files. A classic example is a "module" or "subsystem" that is complex internally but which is simple for a clients to use.The principle is that youonlyput in the header file theminimumnecessary for the clients to be able to use the module. Thatpublishedinformation defines the guarantee functionality that your module is making to the clients. Those guarantees should be preserved over time, as the client and module mutate asynchronously with each other.The less you guarantee, the more freedom you have to change the internal implementation without the client noticing.QuoteCleaner if you could pass the tacho as a variable to the two functions. Alternatively, if each of those two functions needs the instantaneous tacho reading (i.e. not the tacho reading when the function was called), hide the tacho variable inside a function (getTachoReading()) that is published in a header file. That way you are at liberty to enhance/change the tacho reading, should that prove desirable.Be careful if a variable can be updated in one (or more) place and read in other place(s), especially if the variable is longer than the natural word length - that's a classic cause of subtle unreproducable errors. Hiding the variable inside a function(s) can, with care, mitigate that problem.

author: rhb
author_rank: Super Contributor
timestamp: January 21, 2019, 01:46:47 pm
content: Comment delimiter trivia:/***************************************************************\This is a free form comment block. You can make changeswithout having to fiddle with the comment delimiters andit makes the start and end of the comment block obviousto the reader.\***************************************************************/I got the idea from "C Elements of Style" by Oualline, though he left out the top right and lower left slashes. I use it for the file header comment and at the start of some exotic algorithm to explain what it is and provideliterature references.Other good books:The Elements of Programming StyleKerighan and PlaugerC Traps and PitfallsAndrew KoenigExpert C Programming: Deep C SecretsPeter Van Der LindenThe last book shows how to call a function via a pointer to the function. executing a table of function pointers until you hit a null pointer is very useful if at the start of the program you don't know exactly what functions need to be called but after you know it does not change. For example a program that lets the user define the input or output data format.

author: Simon
author_rank: Unknown
timestamp: January 21, 2019, 02:46:51 pm
content: Quote from: GeorgeOfTheJungle on January 21, 2019, 12:56:09 pmNo that won't work. If i include a c file in atmel studio it claims to find the same function twice, I guess because it has already added it to the project so at least one header file with function prototypes is required.

author: mvs
author_rank: Frequent Contributor
timestamp: January 21, 2019, 02:57:27 pm
content: Quote from: Simon on January 21, 2019, 01:08:38 pmNo, it does not.Quote from: Simon on January 21, 2019, 01:08:38 pmYou can include what ever text file you want, preprocessor will just copy the content of it in place of your #include statement. But there is no reason to include *.c files, since they are usually compiled to objects on their own (if you include them in your project or make file). Having multiple definitions of variables and functions may lead to linker errors.

author: legacy
author_rank: Super Contributor
timestamp: January 21, 2019, 03:16:30 pm
content: Quote from: rhb on January 21, 2019, 01:12:57 pmit depends by the toolchain. SierraC does it, gcc&binutils do not since they try to optimize so they removed unused code.this is the reason why for the Not-Yaroze BSP contained in the OpenBIOS of Playstation1 we had to create a dummy module in order to fool the compiler not to remove symbols that you are not using in the BSP whose code was burned in the ROM (yep, we replaced the EPROM with a flash)Concerning applications ... well once the toolchain got stabilized there was no problem, and it was also easy to build the file and serially upload it to the console into the ram.The Japanese version of the Playstation1 has a serial port as well as a port that exposes a window to the CPU's addresses. It was useful for developing a RAM-emulator in order to speed-up the uploads.Unfortunately, then Sony decided to remove both the portsAnyway, the build-up of the application was not tricky, you just needed to link the application with the crt0, lib_BSP (which only contains symbols pointing entities defined in the ROM) and libc (might use BSP entity, or new primitives defined in the libc itself) and you were ready to play.Creating the BSP was a completely different matter because it required a trick. In fact the BSP was contained in the ROM, physically 512Kbyte, mainly written in assembly, with an attached BSP written in C. Assembly has no problem, but the C stuff contains a collection of functions and global variables, and the problem *is* that none or just a few of the functions are invoked by something, and the "main()" was just a empty function to fool the linker, therefore mips1le-softfloat-2.95.1 (yeah, v2.95 .. 10 years ago) was tempted to produce an empty module.At the first attempt, we had more than 200 functions defined in our C sources and, once compiled and linked, we get an empty binary X___XDigging deeper, we found the workaround-trick: we filled the main-function which a list of function calls and global variables initialization, so the linker was happy and didn't remove them.Without this trick, the linker had thought they were "useless" references because never called at compile time.

author: madires
author_rank: Super Contributor
timestamp: January 21, 2019, 03:20:24 pm
content: Typically the .h files are meant to contain stuff which is common to several .c files. But in cases such as a driver for a specific LCD controller a dedicated .h for commands and bits/flags is a good idea also. And I'd recommend to learn about precompiler directives to manage your source.

author: GeorgeOfTheJungle
author_rank: Super Contributor
timestamp: January 21, 2019, 03:35:38 pm
content: Quote from: Simon on January 21, 2019, 02:46:51 pmSorry, I can't help with that IDE, I only know how to use gcc and a text editor, and gcc does not include other files willy-nilly.But you can put the prototype(s) in the .c just before the function with the (forward) function call:Code://Forward call declarationint forwardCall (int);int afunction (void) {[...]return forwardCall(27);}[...]int forwardCall (int a) {[...]}

author: rstofer
author_rank: Super Contributor
timestamp: January 21, 2019, 04:48:59 pm
content: Short answer, yes, you need a .h file for every .c file except main.c. Attempts to #include .c files will ultimately be a disaster - if it even works the first time around.The .h file will have prototypes for public functions and say nothing about private functions inside the .c file. The idea is to provide as little information about the internal workings as possible. Of course, the .h file has to be included in its mating .c file to verify that the prototype matches the actual function.There may be other .h files for things like port definitions and these aren't associated with any particular .c file.Global variables might be considered a bad thing in that any function can access them in any way it wants including changing the value. Maybe the value isn't supposed to be changed by random code. That's the reason that C++ implements private variables and getter/setter functions. The class wants to control access to the variable and its value.Instead of exposing a global variable, you write a function that returns the value and another function that sets the value. Now your getter/setter functions can control what values are stored. Or, you can change the implementation of the getter/setter functions to do something more complex like queue the values (serial port, for example) and the user never needs to know. When some piece of code wants the next char, it doesn't need to know how or where it was stored. It calls the getter function and that's all it needs to know.Yes, hanging a global variable out in the wind is easier, in a Fortran kind of way, but it isn't better.Hide everything, expose little, keep modules isolated through a well defined interface (the .h file).Einstein was right: Code should be as simple as possible. But no simpler!

author: rhb
author_rank: Super Contributor
timestamp: January 21, 2019, 05:02:24 pm
content: @legacyInteresting. All my work was on workstations and super computers. I built stuff using a very large fraction of the development tool chains during the workstation wars. Oil companies tended to buy just about everything. Some of it was gratuitous, but in a lot of instances if you wanted to run a certain 6-7 figure application you bought whatever it ran on. So everyone running reservoir simulations had AIX systems. All the seismic interpreters had Suns. Anyone doing visualization had large SGI systems. And a 3D "cave". Those were cool. Complete immersion with 3D imagery on 3 or 4 walls.But as your post makes clear. Whatever tool chain you are using, you need to know exactly what it is doing. And you have to be prepared for another tool chain to do things completely differently. I can easily see the Gnu tool chain failing because it decided to delete all the functions.Consider the case where you are executing a table of function pointers which gets an initialization from an array if certain run time conditions are met. That's a contrived case though I did do it to speed up user defined format processing of multimillion line text files by a factor of 6.For Simon's benefit I've attached photos of the section of my library devoted purely to C programming. Those were bought back when you could go to a bookstore and read it for 20-30 minutes before deciding to buy it, so they're all good. Some are dated as they deal with porting from 16 bit to 32 bit systems. Of course, now it's 32 to 64, so the techniques are still relevant.The software engineering section is 2-3 times bigger, but the quality is also much more variable. Some things like "Design Patterns", I only bought because the people I was working with were very enamored with it. I'd seen it when it came out, looked at it briefly and put it back on the shelf.@rstofer If the only thing in the .h file is a function prototype you do *not* need a .h for every .c. But including .c files is bad news unless you are working on code which has to run on systems with *extremely* different IOCTL implementations. In that particular case,#ifdef _system_a_#include "system_a_ioctl.c"#endif#ifdef _system_b_#include "system_b_ioctl.c"#endifis useful for avoiding having the "#ifdefs scattered all through a single file.To paraphrase White and Strunk "Sooner violate any of these rules than commit something truly barberous."

author: SimonR
author_rank: Regular Contributor
timestamp: January 21, 2019, 06:26:56 pm
content: The short answer to the question is NO you don't need one header per c file.In fact, and this will really put the cat among the pigeons, you don't actually need header files at all. Although that is probably very bad practice.An explanation of why this is may help you understand how you might choose to use headers.In a C file every item is known to every other item in that file provided it is declared in the file before the item that uses it. The items in this file cannot be seen by any item in any other file.If an item in another file wants to use something in this file, function or variable, then it must use an extern keyword before the line at which it is used.every file that needs to access something in another file needs to use an extern declaration. The same goes for constants, you need a #define in every file that needs it.Now clearly this gets out of hand very quickly and so we choose to put the externs and #defines in a header file so they are declared only once and every file that needs the declaration just includes the right header. Plus you can do lots of other stuff with headers.After that its up to you what you put in the header and how tidy you want to be which is one of the reasons C is difficult to master as there is no right answer.C is a cake you can cut many different ways, all are correct but some are better than others.And before anyone complains about extern being used on functions it became optional when prototypes were introduced. You still needed it in K&R C. You choose to use it in ANSI C or laterThe slightly longer answer is that you tend to need one header per function module, whether the module is made up of 0ne file or many. As you grow your project you will find that the number of C files goes up quickly but the number of headers does not.There's lots of good advice on this already.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: January 21, 2019, 06:36:49 pm
content: Quote from: rstofer on January 21, 2019, 04:48:59 pmThere is no such thing as "better", only more or less appropriate for a given situation.The above would be terrible advice for a tiny microcontroller, or where speed was the most important factor.QuoteGlobal variables are simplest... just sayin'

author: GeorgeOfTheJungle
author_rank: Super Contributor
timestamp: January 21, 2019, 06:45:12 pm
content: Yeah, +1 mike, getters and setters to access globals in an "arduino"? Thanks, but no, thanks...

author: Simon
author_rank: Unknown
timestamp: January 21, 2019, 06:52:12 pm
content: Including a c file will upset many IDE's that have already added the files to the project. But the files will be loaded after the main.c file so the header file is needed to give information on function prototypes.I can't see a way out of global variables when an interrupt routine gives the variable (volatile variable) and another function uses that variable.

author: GeorgeOfTheJungle
author_rank: Super Contributor
timestamp: January 21, 2019, 06:56:54 pm
content: Quote from: Simon on January 21, 2019, 06:52:12 pmYou can put the prototype in the .c file just before the function that calls the forward function, it does NOT need to be in a separate .h file.

author: Simon
author_rank: Unknown
timestamp: January 21, 2019, 06:59:01 pm
content: yes but that makes an awful mess.

author: Jeroen3
author_rank: Super Contributor
timestamp: January 21, 2019, 06:59:49 pm
content: You can usedependency inversion. Both the ISR and the module using the data access this by getter setter of some other module. They both only need to know type the getter setter.Changing anything in either module will not recompile the other module. (since there is no #include)Although this is highly abstract for microcontroller C, once your project gains a certain size these concept might become interesting. And you might regret not having read about them before.Eg: if your project is filling up half a megabyte (without images) the compilation time might start to take minutes or longer depending on the performance of your computer.

author: GeorgeOfTheJungle
author_rank: Super Contributor
timestamp: January 21, 2019, 07:17:30 pm
content: Quote from: Simon on January 21, 2019, 06:59:01 pmWell, you don't have to prototype every function for the sake of prototyping, so, unless you do lots of forward calls there's not going to be lots of them, only the few you need and always next to the functions that need it. But yeah, YMMV.

author: tggzzz
author_rank: Super Contributor
timestamp: January 21, 2019, 07:25:17 pm
content: Be aware that the concept of "dependency inversion" is blindingly obvious to a hardware engineer - you see it in every schematic.In schematic terms it means that you have one schematic for the power supply module, another for the RF front end module, another for the front panel module, and one more for the signal processing module. Each schematic has its module's inputs and outputs clearly delineated as "ports" (or "pins" if the module is an ic!).Then you add a "top-level" schematic consisting of a block for each of those "lower level" modules where the block shows the ports and nothing else, plus wires going between the lower level modules' ports.Unsurprisingly, each "port" is equivalent to something that is exposed in a .h file.It took the software world until the mid-90s to catch up with that concept, and of course they had to give it their own name to make it seem clever.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 21, 2019, 08:26:32 pm
content: An example:lcd_driver.h:Code:#pragma onceextern int variable_that_needs_to_be_so_quick_you_cant_use_a_getter_function;void lcd_init();void lcd_print_text(char* text);lcd_driver.c:Code:#include "lcd.h"int variable_that_needs_to_be_so_quick_you_cant_use_a_getter_function; // Actually defined only herestatic int internal_variable;static void internal_detail(){...}void lcd_init(){internal_detail();}void lcd_print_text(char* text){....internal_variable = 123;}main.c or whatever:Code:#include "lcd_driver.h"void main(){lcd_init();lcd_print_text("hello world");variable_that_needs_to_be_so_quick_you_cant_use_a_getter_function = 123; // Computer science people won't like this way, but you don't need to care.}Compilation, for example:Code:gcc -c lcd_driver.c -o lcd_driver.ogcc -c main.c -o main.old -o out.elf lcd_driver.o main.o(-c means: do not link, only compile this module)It took me about a decade to learn this. This isn't too well taught anywhere. This should be the first freaking C lesson ever, the 101. This is the way C implements private and public member variables and functions. Works very well and is understandable, you need to know two reserved words (staticandextern) and how to use them.Yes, sometimes you could use a single .h and multiple .c, if the interface (.h) is simple, but the implementation (.c) is complex but somehow logical to divide into several .c files. If you only do this to "reduce number of files", you are doing it wrong. If you have too many files and it confuses you, the chances are, your modules are too small, or the overall design is too complex to do what you want it to do.Yes, sometimes you include a .c file from another .c file - one typical example would be an autogenerated dataset like a large lookup table, which is only needed by that specific implementation. By having it in a separate file, it's easier to autogenerate again, overwriting the old file; or choose between files by changing the include line only; or just simply to keep it out of sight. It could be named .whatever, but .c is a logical extension if it's valid C code, such as:sin_lut.c:Code:// Autogenerated, do not modifystatic const int8_t sin_lut ={0, 3, 6, 9, 12, ... };Note that while this could be .h (it's just a file), it would be misleading since this actually defines variables. And, while youcouldcompile it as a separate module and link together at the end, just including the .c to another .c may look simpler; the dependency is so clear and limited. If you did it through the linker, then you'd need to to declare the variable somewhere, preferably in another .h file, which would look stupid for such a trivially small task of including one constant table which wouldobviouslybe in the single .c file directly if it was smaller and human-generated.

author: rhb
author_rank: Super Contributor
timestamp: January 21, 2019, 09:19:32 pm
content: Quote from: Siwastaja on January 21, 2019, 08:26:32 pmThat's a very important point. There are cases where the only sensible implementation is to use a program that writes a larger piece of code. lex and yacc are classic examples.

author: westfw
author_rank: Super Contributor
timestamp: January 21, 2019, 11:18:35 pm
content: I think that nominally, you need TWO .h files for each "set" of .c files.So if you have, say, a parsing library with separate C files for text, numbers, and keywords, and internal stuff, you might have the 4 .c files, plus a "parse_api.h" file that exposes the API to all the clients, and a "parse_internal.h" that contains all the other stuff needed for the library itself to compile (protoctypes of internal functions, or perhaps only includes of the parse_api.h, if you're exposing everything.)Arduino turned on the switches that cause unused code to be omitted from the binary (even if they're in the same .C file as used code) about a decade ago. If your compiler still doesn't have a similar option, perhaps it's time to consider a new compiler. (that said, I bought a network protocol library a couple decades ago that put every function in a separate .c file, and had nearly as many .h files. While it was a bit of a mess to look at, it worked REALLY WELL. (and: if your editor can't easily jump between files to find a different function, perhaps it's time to get a new editor.)

author: legacy
author_rank: Super Contributor
timestamp: January 21, 2019, 11:44:59 pm
content: Quote from: westfw on January 21, 2019, 11:18:35 pmthis is done not by the compiler but rather by the Wiring &C infrastructure.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 21, 2019, 11:52:55 pm
content: .h file it's just a file being included in the compilation at the #include -exactlyas if you copied and pasted. This is all there is to it (setting aside IDEs, syntax highlighters and such). It doesn't have to be .h, can have any extension you wish.Thus, every time you want to include the same text into two (or more) different files, you use the .h file. Or, if you want to generate a text externally and then include into the file being compiled, you use the .h file as well.

author: rhb
author_rank: Super Contributor
timestamp: January 22, 2019, 02:17:36 am
content: Quote from: NorthGuy on January 21, 2019, 11:52:55 pmThis is *really* important! I use the C preprocessor to include named COMMON in FORTRAN to guarantee that *all* named COMMON blocks are the same. It's one of the first things I do if I build a dusty deck. I have *never* encountered a large code that did not have errors in named COMMON blocks.It is essential that you understand both the preprocessor rules in the language standard *and* whatever the implementation you are using actually does. The preprocessor is commonly a separate program, but that it not required.Imake was written with the assumption that the C preprocessor could be used to modify files for different systems. In the end, imake(1) had to include its own C preprocessor. I always thought the snake on the cover of the O'Reilly imake book especially appropriate. I accomplished the same thing using Gnu make at a fraction of the complexity level. The makefiles included a single file with a name derived from uname(1). Everything else was just very basic make syntax.

author: westfw
author_rank: Super Contributor
timestamp: January 22, 2019, 02:36:48 am
content: QuoteNope. Standard gcc compiler switches.-ffunction-sections -fdata-sections(Compiler)-gc-sections(linker)https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.htmlHuh. The documentation implies that this can be detrimental to program size, since it apparently defeats intra-file optimization. That hasn't been my experience (for example, functions are still inlined when they are tiny.) Perhaps additional link-level optimizations (-flto, -relax) fix it up...

author: andyturk
author_rank: Frequent Contributor
timestamp: January 22, 2019, 03:45:46 am
content: Simon,Ditch your IDE. It's not helping you learn how this stuff works.These are really basic questions and if you were to spend a day writing batch scripts, makefiles, or whatever using only command line tools, you'd be able to master these concepts. As it is now, your understanding seems to stop at what the IDE does and that's simply not sufficient for professional work.Back to the topic... once you've factored out some code into a (shared) header file, you have to make sure that when that when the header is modified, every .c that depends on it is recompiled. Do you know how to do that without relying on an IDE?

author: rhb
author_rank: Super Contributor
timestamp: January 22, 2019, 04:11:21 am
content: Quote from: andyturk on January 22, 2019, 03:45:46 amThis is *excellent* advice. I wish I'd thought of it. I do not, and will not use an IDE if I have a command line option. There is no substitute for knowing *exactly* what is being done. The fundamental premise of an IDE is to hide all that. Starting out it makes it easier. Long term it makes it a lot harder.Gnu make has become a bit feature rich, but it is consistent across all platforms Not that many of the others still exist. But 25 years ago, every OEM had a *just* slightly different make implementation. I'm disturbed that they are all gone, but there is nothing to be done about it. Once IBM committed to investing $1 billion in Linux it was all over but the fire sales.

author: andyturk
author_rank: Frequent Contributor
timestamp: January 22, 2019, 05:23:06 am
content: Quote from: rhb on January 22, 2019, 04:11:21 amAnd under the covers, GNU Make often does the heavy lifting for contemporary IDEs. Sure, it's weird and arcane, but it's also super stable and available on all major development platforms. TheGNU Make Standard Libraryhas lots of good functionality that can add robustness to a build system.

author: tggzzz
author_rank: Super Contributor
timestamp: January 22, 2019, 09:10:11 am
content: Ah yes, makefiles. Haven't written one in decades.Do they still have the "feature" that tabs have a different meaning to spaces?

author: legacy
author_rank: Super Contributor
timestamp: January 22, 2019, 11:38:01 am
content: gnu make =sys-devel/make-4.2.1-r4 (last one stable) is *NOT* exactly compatible with its predecessors about expanding certain macros, and this is a problem with Linux kernel <v2.6.23, when you have to *edit* the "Makefile" and replace all the affected linesnot a real problem, just 15 minutes of your timethe real problem is: gnu make is NOT exactly compatible with SGI MIPS/PRO make, as well as gnu grep is NOT compatible with Unix grep, as well as GNU Tar is not compatible with UNIX Tartar is what is really ... annoying, especially if you have used it for tarballing something on a tape drive with IRIX and years later you want to untarball it with LinuxI have DDS4 tape drive, it's a common SCSI tape drive, with a common SCSI protocol, so Linux should use it successfully, and it does it, unfortunately, the problem is in the format of the tarball archive; a customer sent me backup tapes and ... I have to get a goddamn IRIX machine in order to read them.Someone in GNU must have thought that it would have been "cool" improving the UNIX Tar into GNU Tar

author: glarsson
author_rank: Frequent Contributor
timestamp: January 22, 2019, 11:54:58 am
content: Quote from: legacy on January 22, 2019, 11:38:01 amRemember that GNU stands for GNU's Not Unix.We have standard POSIX make and Gnu make.We have standard POSIX tar and Gnu tar.GNU suffers from severe Not Invented Here syndrome.

author: andyturk
author_rank: Frequent Contributor
timestamp: January 22, 2019, 01:33:06 pm
content: Quote from: tggzzz on January 22, 2019, 09:10:11 amYou betcha.I cut the program some slack because that design decision was made in 1976. OTOH, it's unfortunate that the Python world failed to learn from the experience and believes "significant" whitespace is a good idea.PS. Managing tabs in makefiles is easy if you're using emacs.

author: tggzzz
author_rank: Super Contributor
timestamp: January 22, 2019, 02:23:18 pm
content: Quote from: andyturk on January 22, 2019, 01:33:06 pmJust so.The software world is unusually prone to repeating old historical mistakes for new "reasons" - they are condemned to repeat history because they don't know it. My least favourite at the moment is the trend to "flat" GUIs where you can't know whether the black tab/button is active or not. That was a pain in the 80s, solved by X11's Motif (from HP of all companies!). Now it it a pain again.

author: rstofer
author_rank: Super Contributor
timestamp: January 22, 2019, 04:21:29 pm
content: Quote from: tggzzz on January 22, 2019, 09:10:11 amAFAIK, yes!One approach for larger files is to put large modules in subdirectories, suitably named. It is then possible for the top level makefile to recurse through the subdirectories invoking smaller makefiles in each subdirectory. This seems like the long way around but the makefiles in the subdirectories are usually just a copy and paste.Check out the structure of the code athttp://jcwren.com/arm/The makefiles are worth a glance.So, I put my UART code in a file and I need to tell the world which functions are available for use. I create a .h file to form a contract between the caller and the callee. I don't put function prototypes in the .h file unless I really want them to be callable. In the .c file, I can declare internal functions as 'static'. This also prevents name conflict with identically named functions in other .c files. If I need a prototype within a .c file just due to forward referencing, I'll put it in the .c file but not in the .h file.I would put the initialization code, the interrupt handler and the UART get/put functions in the file but I probably wouldn't publish prototypes for the interrupt code. The user doesn't need to know. He also doesn't need to know that I use a circular queue. All he needs to know is what I tell him in the .h file and that would be the bare minimum.As to 'tiny' CPUs and global variables, well, if they're really tiny we are probably coding in assembly language and everything will be visible.I'm firmly in favor of hiding as much of the implementation as possible. If the user can't get at a function or variable, they can't misuse it.Of course there are exceptions, there aren't any 'laws', just guidelines.Global variables are required from time to time but they shouldn't be used just to avoid having to come up with a better program structure.Then we get to the idea of dependencies. You change a .c file and then change the .h to match. How many files in the project need to be recompiled? The IDE that builds a makefile knows very well how to check dependencies and is perfectly capable of compiling just the files that include the .h file. User makefiles can also build dependencies and at least one of mine creates the dependencies and appends it to the top level makefile.I have attached a makefile. Note that the target 'all' depends on 'depend' which creates the list of dependencies at the bottom of the file. This gets executed on every build and this is probably not the best way to do it. But it works!This makefile is used from a Linux command line, my preferred way to build projects. I use gedit as the editor (because it is WYSIWYG) and make.Unfortunately, makefiles can't be attached without adding a file extension.

author: andyturk
author_rank: Frequent Contributor
timestamp: January 22, 2019, 10:02:44 pm
content: Quote from: rstofer on January 22, 2019, 04:21:29 pmCool! It's always interesting to see how others do things.If you add "-MD" to your CFLAGS, GCC will generate the dependency info for you, producing a .d file for each object file (.o) it produces. The .d files are actually GNU Make include files, which you can bring in easily with:Code:-include $(OBJS:%.o=%.d)I tend to lean heavily on make and use a few "weird tricks" to expand its capability. One is burying other scripts within a Makefile and then piping those scripts into shell tools. Here's one that handles flashing via JLink:Code:########################define FLASH_BARE_CMDdevice $(SEGGER_DEVICE)log $(JLINK_LOG) # log what we dosi SWDspeed 4000hloadbin $(EXECUTABLE:.elf=.bin) 0x0rgexitendef#########################export FLASH_BARE_CMDjflash : $(EXECUTABLE:.elf=.bin)@echo "$$FLASH_BARE_CMD" | $(JLINKEXE) $(SELECT_JLINK)So "make jflash" will compile, link and then reprogram the mcu.

author: rstofer
author_rank: Super Contributor
timestamp: January 23, 2019, 01:30:14 am
content: Quote from: andyturk on January 22, 2019, 10:02:44 pmThanks for the tip! I'll try it next time I create a makefile - if I don't lose the text I just copied from the reply.My target 'make program' used the lpc21isp utility to program the flash. All the parameters are on the command line. This target depends on the .hex file which, in turn, causes all necessary compilations and linking.Then create a keyboard alias like 'alias p=make program' and I have a one keystroke solution to build and program the flash.When it comes to writing code, Linux is a very productive environment.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: January 26, 2019, 11:09:33 am
content: It's all up to you.Do you want to hack something quick together, or do you want to put in some effort to be able to read your own code 5 years into the future?Do you want to re-use some "library" files such as for example the functions to use an encoder or serial port for other projects?I find it much easier to give each .c file it's own .h file.It avoids the fud of what is where.If you use a decent IDE it is trivial anyway.It just generates a .c and .h file from a template, which already has the include guards in the header file.While browsing your project it also does not matter if you add 20 header files to the 20 source code files. If you follow convention and common sense you put the source in the source file and the miscellaneous info in the header files.If you want to obfuscate, you put some the source in .h files, other source in .bin files, put the stuff that normally goes into a header file on lines between functions in a source code file where it does not belong and write a makefile to stich it all together.Or you use the arduino way. Write a script to dump everything into a single file before you pass it to the compiler.

author: GeorgeOfTheJungle
author_rank: Super Contributor
timestamp: January 26, 2019, 02:28:44 pm
content: Quote from: Doctorandus_P on January 26, 2019, 11:09:33 amThat's the first thing gcc does, when it (automatically) calls cpp.https://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html