Author Topic: General C programming question  (Read 8305 times)

0 Members and 1 Guest are viewing this topic.

Offline NW27Topic starter

  • Regular Contributor
  • *
  • Posts: 53
  • Country: au
General C programming question
« on: July 19, 2018, 12:43:54 pm »
Hi,
Q1. In the source file that has the main() function, is there any particular reason to have the function prototypes followed by the main() followed by the actual functions?
Why not just put the complete functions above the main and dispense with the prototypes completely?
The only thing I have encountered is that you need to be mindful of the function ordering ie called functions must be above the calling code. Otherwise I do not see any advantage.
Note - I was a pascal programmer before becoming a C programmer.

2. How many people have a header file for the main() source file and what do you use it for?

Thanks,
Neil.
 

Offline NivagSwerdna

  • Super Contributor
  • ***
  • Posts: 2495
  • Country: gb
Re: General C programming question
« Reply #1 on: July 19, 2018, 12:55:47 pm »
1. Declaring explictly is handy for clarity but also allows forward references which can be useful is a and b call each other.

2. Mostly. Putting the declarations in a header file means they can be reused elsewhere and sprinkling #ifdef to prevent issues with being #included more than once.
 

Z80

  • Guest
Re: General C programming question
« Reply #2 on: July 19, 2018, 01:03:01 pm »
Protoypes allow you to use functions before they are defined (many reasons to do this).  For a simple program with everything in order you don't need to use them (unless you are using a really old pedantic compiler).

Headers are there for global declarations.  Declare global variables, includes and function prototypes.  Again for a simple program it probably seems overkill, but when things get more complex, they are very useful.
 

Offline Kjelt

  • Super Contributor
  • ***
  • Posts: 6460
  • Country: nl
Re: General C programming question
« Reply #3 on: July 19, 2018, 01:17:15 pm »
In a main.c file it is more readable when the first actual function is the main() itself. First digging through all the other functions is a bit PITA.
Usually a main.c file has no or very little functions, it does call lots of functions, sometimes a statemachine that kind of stuff.
Mostly for a good architecture you call the functions from other c files, the functions are defined in the header files which is the answer to your second question.   
Almost every main.c file has header files on top.

Why use other c files and header files ?
For a lot of reasons most important I can think of : clearity and re-usability.
See it like this, if you make functions you use often like helper functions or aka service / support functions, put them in a seperate support module.
You can use it each and every program you write, just include the h file and include the c file in your project.
I do this all the time, if I pick a new chip for instance a I2C clock IC or whatever I write the interface code in a seperate C file with the filename = IC name.
If i need the chip in some product I just copy the icname.c and h file in the new software, done.
I could not code without header files, you get crazy big main.c file with spaghetti code, unreadable for anyone else and totally not re-usable for the next project.

BTW this has little to do with C alone, with Pascal, C++, C#, Java just to name a few it is all the same.
 
The following users thanked this post: rs20

Online NorthGuy

  • Super Contributor
  • ***
  • Posts: 3142
  • Country: ca
Re: General C programming question
« Reply #4 on: July 19, 2018, 01:33:57 pm »
Delphi has single-file units, which has two parts - interface and implementation.

In C, this typically corresponds to two files:

.h file contains everything which you would put into the "interface" section in Delphi unit.
.c file contains things which you would put into the "implementation" section.

When you want to use a compilation unit, such as

Code: [Select]
uses Something, SomethingElse;
you simply include the .h file

Code: [Select]
#include "Something.h"
#include "SomethingElse.h"

However, in C, the .h file is simply an include. You may include anything, such as a sine lookup table, for example.

Functions don't need separate declaration, but if your functions include circular calls (such as A calls B and B calls A), you will need separate declarations. This is the same as "forward" declarations in Delphi.

I don't usually have an .h file for the main.

 

Offline IonizedGears

  • Regular Contributor
  • *
  • Posts: 248
  • Country: us
Re: General C programming question
« Reply #5 on: July 19, 2018, 05:41:23 pm »
In C, scope generally extends downwards i.e. things can only see the things that are above them, not below.

Function prototypes put visibility at the top so that all of the functions within a program are aware of all of the other functions. Code structure, and more importantly, code restructuring becomes a nightmare without prototypes because you will have to order your functions so that they are below the other functions that they depend on in your program. Imagine what happens when you try to change your program. This becomes worse and worse as you add more functions so it's much better to just add prototypes than adding another headache.

Bigger programs will use header files for the prototypes so that visibility can extend to other files.

Sorry for any errors, typed this out with my phone. IX

I am an EE with interests in Embedded, RF, Control Systems, and Nanotech.
 

Online ajb

  • Super Contributor
  • ***
  • Posts: 2599
  • Country: us
Re: General C programming question
« Reply #6 on: July 19, 2018, 05:57:21 pm »
2. How many people have a header file for the main() source file and what do you use it for?

If you mean a "main.h" that is only included by main.c, not usually. 

Generally the point of moving declarations out of a .c file (really a compilation unit) and into a header is so you can make other compilation units aware of information or functionality that the first compilation unit provides.  So if you have a library that your application uses, your library's public interface, which is the collection of functions and variables that your application must call or access, should be declared in a header file that you include in your application's files wherever the library functionality is used.  The declarations for functions and variables used only within the library may remain in the c file if the library is simple, or for more complex multi-file libraries you might have another header file that is only included within the library. 

So because main.c canonically does not provide functionality to anything outside itself, it's not necessary to declare any of its contents to any other compilation unit.  So the only benefit to moving main.c's declarations to main.h is to hide them from main.c.  Now you might have a bunch of functions or some global variables that are used all over your application, and in that case you may well have a project-specific header that contains all of those declarations, and perhaps has its own includes for libraries that are used everywhere in the project.  However, this is a bit of a code smell, indicating that the project probably does not exhibit good encapsulation or decoupling, and it's easy to wind up with circular dependencies this way. 
 

Offline apblog

  • Regular Contributor
  • *
  • Posts: 108
  • Country: us
Re: General C programming question
« Reply #7 on: July 19, 2018, 06:11:33 pm »
It is very common to find C programs with main() at the bottom of the module, for the reason you describe. There is nothing wrong with this style.
 

Online rhb

  • Super Contributor
  • ***
  • Posts: 3481
  • Country: us
Re: General C programming question
« Reply #8 on: July 19, 2018, 07:18:34 pm »
It is very common to find C programs with main() at the bottom of the module, for the reason you describe. There is nothing wrong with this style.

Quite agree.  And I've maintained a few million lines of other people's code.  Generally a main.h file will be used if main.c only contains main() and perhaps some functions or variables which are declared static at file scope so that they cannot be seen outside of main.c.  In large programs where main is many lines this allows interposing a debugging function between main() and another function without affecting any other files that might reference that function.  You can also produce the same result by partially linking the interposed function and the function where you want to reference it, though you may generate some warnings or encounter final link problems.  It's pretty system dependent.

Generally, once a file exceeds a few hundred lines, it's time to consider dividing it up.  And libraries should always be a single function per file unless you have functions which share a file scope global variable.  If multiple functions are declared in a single file, referencing any function from that file will include *all* the functions in the file.  This makes for considerable memory consumption at run time.
 

Offline Kjelt

  • Super Contributor
  • ***
  • Posts: 6460
  • Country: nl
Re: General C programming question
« Reply #9 on: July 19, 2018, 08:35:55 pm »
And libraries should always be a single function per file unless you have functions which share a file scope global variable.  If multiple functions are declared in a single file, referencing any function from that file will include *all* the functions in the file.  This makes for considerable memory consumption at run time.
Really? One function per file? Are you kidding?  :palm:
 

Online rhb

  • Super Contributor
  • ***
  • Posts: 3481
  • Country: us
Re: General C programming question
« Reply #10 on: July 19, 2018, 10:05:41 pm »
S
And libraries should always be a single function per file unless you have functions which share a file scope global variable.  If multiple functions are declared in a single file, referencing any function from that file will include *all* the functions in the file.  This makes for considerable memory consumption at run time.
Really? One function per file? Are you kidding?  :palm:

I most certainly do.  The linker doesn't include just the function you reference.  It includes the entire file.  It has to because of the scoping rules.  All those things in the language standards that most programmers never read have profound consequences for how things *must* be done.

So if someone foolishly places all the functions in a large library in one file, the system has to allocate space for the entire library to run the program.  I've had to fix this when taking over million line codebases by writing a program to break the file up into pieces.  I did it because people were unable to run the programs because they ran out of memory on $30-40K workstations.

 I'm running Hipster, the OpenSolaris derivative.  The xclock clone showing the date and time  consumes 80 MB of memory!  On Solaris 10 u8 it takes 6 MB.  I don't have a Sun 3/60 any longer, but I can assure you that xclock did not consume 30% of the16 MB memory on it.

All of this is a consequence of programmers who do not actually know what is happening at all levels when they write a line of code. I've made a lot of money fixing the mistakes engendered by their ignorance.
 

Offline NW27Topic starter

  • Regular Contributor
  • *
  • Posts: 53
  • Country: au
Re: General C programming question
« Reply #11 on: July 19, 2018, 10:20:28 pm »
Hi
Thanks for all of the responses.

I gotta say, coming from a Pascal environment (BP, Delphi & AVRCo), I'm quite disappointed by the embedded C compilers and their evolution.
Examples
1.  The compilers do not optimize out unused functions.

2. No startup/ shutdown code functions for individual source code files ie an I2C Display init unit.

3. No further development of the language for pascal style interface and implementation areas ie local global variables that can only be seen within the local source file.

The list could go on for quite a while.
There have been changes to enhance functions ie c++ but not enhancements to the not specifically IDE but to the higher level code functionality. 

Sent from my SM-N920I using Tapatalk
« Last Edit: July 19, 2018, 10:29:01 pm by NW27 »
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: General C programming question
« Reply #12 on: July 19, 2018, 10:23:48 pm »
>
Quote
libraries should always be a single function per file unless you have functions which share a file scope global variable.  If multiple functions are declared in a single file, referencing any function from that file will include *all* the functions in the file.
Or, you could use a more modern C compiler that doesn't do that.See the -ffunction-sections compiler option and -gc-sections linker option of gcc, for example.(If your compiler DOESN'T support such a feature, the single-function-per-file is good advice, assuming that the functions are distinct.  Often a high-level library won't benefit very much from such shenanigans.)
(And ...  this is a way that C++ causes code bloat.  Virtual functions ... blah blah ... will wreck havoc with they "I used that function" logic, and end up including the function from the base class whether it's ever called, or not...)  (Unless there's a magic compiler option I missed that prevents that!)
 

Offline glarsson

  • Frequent Contributor
  • **
  • Posts: 814
  • Country: se
Re: General C programming question
« Reply #13 on: July 19, 2018, 10:31:29 pm »
...ie local global variables that can only be seen within the local source file.
p
Place the variable at file scope (not inside a function) and declare it static. The variable will be seen inside functions in this file but will not be visible to other functions.
 

Online rhb

  • Super Contributor
  • ***
  • Posts: 3481
  • Country: us
Re: General C programming question
« Reply #14 on: July 20, 2018, 01:43:08 am »
It's not the compiler.  It's ld(1) and ar(1) in a Unix environment.  I was paid very well for knowing in great detail who did what to whom.

Unfortunately, Gnu/Linux is increasingly not Unix. So I won't say what it does. They keep randomly changing option flags and everything else, just because they can.  It's *really* annoying to have a complex shell script blow up because some fool has changed the semantics of some option.  Linux is to Unix what Windows NT is to Linux.  Nice try.
 

Offline NW27Topic starter

  • Regular Contributor
  • *
  • Posts: 53
  • Country: au
Re: General C programming question
« Reply #15 on: July 20, 2018, 06:25:24 am »
It's not the compiler.  It's ld(1) and ar(1) in a Unix environment.  I was paid very well for knowing in great detail who did what to whom.

Unfortunately, Gnu/Linux is increasingly not Unix. So I won't say what it does. They keep randomly changing option flags and everything else, just because they can.  It's *really* annoying to have a complex shell script blow up because some fool has changed the semantics of some option.  Linux is to Unix what Windows NT is to Linux.  Nice try.
Sorry, I don't understand.

Sent from my SM-N920I using Tapatalk

 

Offline donotdespisethesnake

  • Super Contributor
  • ***
  • Posts: 1093
  • Country: gb
  • Embedded stuff
Re: General C programming question
« Reply #16 on: July 20, 2018, 06:33:37 am »
All of this is a consequence of programmers who do not actually know what is happening at all levels when they write a line of code.

Yes, there are a lot of those about. They are often found giving their "expert advice" on web forums.  :-DD
Bob
"All you said is just a bunch of opinions."
 

Offline Kjelt

  • Super Contributor
  • ***
  • Posts: 6460
  • Country: nl
Re: General C programming question
« Reply #17 on: July 20, 2018, 07:41:52 am »
I most certainly do.  The linker doesn't include just the function you reference.  It includes the entire file.  It has to because of the scoping rules.  All those things in the language standards that most programmers never read have profound consequences for how things *must* be done.

So if someone foolishly places all the functions in a large library in one file, the system has to allocate space for the entire library to run the program.  I've had to fix this when taking over million line codebases by writing a program to break the file up into pieces.  I did it because people were unable to run the programs because they ran out of memory on $30-40K workstations.

 I'm running Hipster, the OpenSolaris derivative.  The xclock clone showing the date and time  consumes 80 MB of memory!  On Solaris 10 u8 it takes 6 MB.  I don't have a Sun 3/60 any longer, but I can assure you that xclock did not consume 30% of the16 MB memory on it.
I am not saying to put everything in 1 file, just all the functions that belong to a certain cluster or component otherwise you get other problems like in the next project someone will forget to include all the files.

So for instance your clock example should be a clock.c file with SetClockTime(..), GetClockTime(..) perhaps SetClockTimeZone(..), GetClockTimeZone(..) etc.
Those functions should all be included in the same component, eg c file. You are NOT going to make 4 or more seperate c files from those functions that is ludicrous and from the 6 companies I worked for over the past 20+ years I luckily never ever seen that. A C programmer that did that, would have been fired the same week.

But seperating other functionality that in your example probably was stuck in to one bloat file that consumes masses of ram, like utils or something like that,  that is also not how it should be.

Sidenote 1: modern compilers can optimize unused functions out, always check esp in embedded projects or your GUI bloat example.
Sidenote 2: self modifying Unix/Linux libraries can be a PITA since every new release you have to do it over and over again or not update which might be worse.
                  Instead you should raise a ticket with the maintainers and ask for seperation of functionality in more than one library due to exhaustive memory usage.
 

Online newbrain

  • Super Contributor
  • ***
  • Posts: 1719
  • Country: se
Re: General C programming question
« Reply #18 on: July 20, 2018, 08:08:51 am »
Hi
Thanks for all of the responses.

I gotta say, coming from a Pascal environment (BP, Delphi & AVRCo), I'm quite disappointed by the embedded C compilers and their evolution.
Examples
1.  The compilers do not optimize out unused functions.

2. No startup/ shutdown code functions for individual source code files ie an I2C Display init unit.

3. No further development of the language for pascal style interface and implementation areas ie local global variables that can only be seen within the local source file.

The list could go on for quite a while.
There have been changes to enhance functions ie c++ but not enhancements to the not specifically IDE but to the higher level code functionality. 

Sent from my SM-N920I using Tapatalk
I've gotta say, coming also from a Pascal environment (but that was some 35 years ago...), that a deeper knowledge of C is needed before judging.
It takes some time and effort to switch thought patterns that have become habit.
I would not advocate one over the other (I have my own preference) but:
  • They mostly do at the link stage, see westfw post above. At compile time, unless the program is compiled as a whole, is very difficult (read: impossible) to know what will be used and what will not.
  • C leaves that to your good will and discipline. C++ has of course constructors and destructors. Standard Pascal does not have any of this, Extended (ISO10206) I think does (if you can find a compiler), the rest are proprietary extensions...
  • Scope, linkage, name space and storage duration rules are described in chapters 6.2.1, 2, 3, and 4 of the C standard. They are varied and powerful enough for most tastes, and definitely cover your example.

The list could go on for quite a while. >:D
Nandemo wa shiranai wa yo, shitteru koto dake.
 
The following users thanked this post: agehall

Online rhb

  • Super Contributor
  • ***
  • Posts: 3481
  • Country: us
Re: General C programming question
« Reply #19 on: July 20, 2018, 01:16:44 pm »

I am not saying to put everything in 1 file, just all the functions that belong to a certain cluster or component otherwise you get other problems like in the next project someone will forget to include all the files.

So for instance your clock example should be a clock.c file with SetClockTime(..), GetClockTime(..) perhaps SetClockTimeZone(..), GetClockTimeZone(..) etc.
Those functions should all be included in the same component, eg c file. You are NOT going to make 4 or more seperate c files from those functions that is ludicrous and from the 6 companies I worked for over the past 20+ years I luckily never ever seen that. A C programmer that did that, would have been fired the same week.

But seperating other functionality that in your example probably was stuck in to one bloat file that consumes masses of ram, like utils or something like that,  that is also not how it should be.

Sidenote 1: modern compilers can optimize unused functions out, always check esp in embedded projects or your GUI bloat example.
Sidenote 2: self modifying Unix/Linux libraries can be a PITA since every new release you have to do it over and over again or not update which might be worse.
                  Instead you should raise a ticket with the maintainers and ask for seperation of functionality in more than one library due to exhaustive memory usage.

I think we're talking about software on very different scales.  For small programs it doesn't matter.   And if you're using globals with file scope they have to be in the same file.

Generally I put everything in one file for small programs.   At a certain size, I split things up into related pieces. If I want to use something in another program I put it in a library so there is only a single version to maintain.   

Having had to maintain a 500,000 line code base which had been written by copy and modify with the same bugs repeated in a dozen functions all with different names, I shudder at the idea that everything should be in one file because someone might forget to include the function.  They'll find out it's missing as soon as they try to link.  And if it's used in more than one place it belongs in a library.

Big packages like 500,000 line seismic processing systems with a dozen libraries are a whole other kettle of fish.  For things of that size you need to be very aware of how the linker operates and how the libraries are structured internally.

I think there's been considerable confusion in this discussion between the compilation and linking phases.  I'm an old VMS and Unix programmer.  I don't use Windows for programming work and I don't use IDEs for anything.  I strictly use an editor, make and the command line. 

If you're doing mixed language (C, C++ and FORTRAN)  programming you have to understand exactly how the linker works, what options are required, etc. You also need to understand the operation of the compilers at a similar level of detail.  During the workstation wars of the 80's and 90's that meant i had to know how these worked on 6 different systems for a  job porting a single package from VMS to Unix.

Seismic processing is both compute and I/O intensive.  A common operation done on all data requires in the simplest case summing at least several hundred thousand  values into each output value for many millions of output samples.  I read compiler optimization books, not because I'm going to write a compiler, but because I need to know in great detail the hardware level implications of the various optimizations.  I pay attention to cache associativity as a careless choice of memory access can flush the cache line on every access.  So in the case of a 16 byte cache and a 4 byte variable the data transfer between main memory and the cache goes up by a factor of 4x what it should be.  And the longer the cache line, the worse it gets.

I learned all this in the process of fixing problems in several million lines of other people's code.  Most of it written by scientists with few if any comments.  In one case I had to find and read a journal article just to find out whether the function was operating in the time or frequency domain.  Once I found out what it did I rewrote it.  It was probably 30-40 lines.

 I learned about the linker behavior from a set of libraries which had every function in the library in a single file.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4028
  • Country: nz
Re: General C programming question
« Reply #20 on: July 20, 2018, 01:41:50 pm »
Q1. In the source file that has the main() function, is there any particular reason to have the function prototypes followed by the main() followed by the actual functions?

I've never done this. I've maintained millions of lines of code and I don't even remember seeing it done since maybe the 1980s -- I'd say since ANSI C came out.

Except for actual .h files, I never write a prototype for a function separately from the function except to break actual mutual recursion. I take seeing a prototype internal for a file as a signal that there *is* mutual recursion involving that function prototype and the immediately following function -- and I'll expect to see the implementation right after that.
 

Online NorthGuy

  • Super Contributor
  • ***
  • Posts: 3142
  • Country: ca
Re: General C programming question
« Reply #21 on: July 20, 2018, 01:54:31 pm »
I gotta say, coming from a Pascal environment (BP, Delphi & AVRCo), I'm quite disappointed by the embedded C compilers and their evolution.

Yes, Pascal (I only used BP/Delphi) is better organized than C. If you don't use strings, but rather use C-like PChar, it can do practically the same as C, but it is more convenient than C.  It also much easier for the compiler, so your compiles get faster (old Delphi compiler can compile 300K lines in less than a second).

However, C is more universal, so often you don't have much choice but using C.

Once you get used to C quirks, it is not that much different from Pascal.

 

Offline andyturk

  • Frequent Contributor
  • **
  • Posts: 895
  • Country: us
Re: General C programming question
« Reply #22 on: July 20, 2018, 02:01:10 pm »
If multiple functions are declared in a single file, referencing any function from that file will include *all* the functions in the file.  This makes for considerable memory consumption at run time.
The GNU linker will happily include just the code that's actually called (as far as it can tell) and strip away unreachable code from the binary. To do that, you have to tell the compiler (via the "-ffunction-sections") option to put each function in its own "section" and then pass the "--gc-sections" option to the linker. That's basically the standard on any microcontroller build where memory can be at a premium.
 

Offline andyturk

  • Frequent Contributor
  • **
  • Posts: 895
  • Country: us
Re: General C programming question
« Reply #23 on: July 20, 2018, 02:12:02 pm »
[Pascal] also much easier for the compiler, so your compiles get faster (old Delphi compiler can compile 300K lines in less than a second).
I see a lot of people here talk about compile/build time, which seems surprising. This is an embedded forum, so the builds we're talking about aren't that big--it's not like you have to compile the linux kernel each time. Is the difference between a 30 second build and a 2-minute build really important? Seems like the impatience of youth, IMO.

Sure, when you're chasing that bug at 2am and on your 2nd Red Bull, that extra 90 seconds feels like an hour.  :o
 

Online rhb

  • Super Contributor
  • ***
  • Posts: 3481
  • Country: us
Re: General C programming question
« Reply #24 on: July 20, 2018, 02:26:04 pm »
If multiple functions are declared in a single file, referencing any function from that file will include *all* the functions in the file.  This makes for considerable memory consumption at run time.
The GNU linker will happily include just the code that's actually called (as far as it can tell) and strip away unreachable code from the binary. To do that, you have to tell the compiler (via the "-ffunction-sections") option to put each function in its own "section" and then pass the "--gc-sections" option to the linker. That's basically the standard on any microcontroller build where memory can be at a premium.

Does nm(1) show you that unused functions are absent? 

The widespread use of the Gnu toolchain  for MCUs may well have resulted in the linker behavior changing.  With MCU makers funding work on the Gnu dev tools it would make sense to do that.  What actually happens when you pass those flags may be target dependent.  TI only recently started supporting the Gnu toolchain for the MSP430 with internal staff.  So I would not be at all surprised if it varied from device to device.

The only way to be sure is to look at the symbol table with nm(1) and see whether the unused  text segments are present.  There may be symbol table entries created by function prototypes, but those will go away when the symbol table is stripped.  What matters is are the text segments there.

I ran into the problem 25 years ago.  So I just adopted work habits that avoided it.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf