Thread: Source file organization (best practice)

  1. #1
    Registered User
    Join Date
    Apr 2020
    Location
    Greater Philadelphia
    Posts
    26

    Source file organization (best practice)

    Prior to main(), a C source file might contain:
    #pragma..
    #include...
    #define...
    typedef...
    global variables
    function prototypes (headers)
    functions?

    and probably half-a-dozen other things I've not thought of offhand.

    Have I listed these elements, by and large, in the order they should
    usually appear in the file? Other than defining/declaring an object before it can be referenced, what principles should be borne in mind?

    Whether to put functions used in main() before or after main() is, I would guess, a matter of taste, although if they follow main() you must provide a prototype.

    Thanks for any thoughts from experts.

  2. #2
    Registered User
    Join Date
    May 2010
    Posts
    4,630
    Have I listed these elements, by and large, in the order they should
    usually appear in the file?
    Actually, IMO, global variables should not normally appear in a file since they should rarely be used if they're used at all.

    Whether to put functions used in main() before or after main() is, I would guess, a matter of taste, although if they follow main() you must provide a prototype.
    True, but using a prototype and placing the functions in (before or after main()) the main source files makes it easier to separate those functions into their own source/header files when programs start getting larger.

  3. #3
    Registered User
    Join Date
    Apr 2020
    Location
    Greater Philadelphia
    Posts
    26
    Quote Originally Posted by jimblumberg View Post
    global variables should not normally appear in a file since they should rarely be used if they're used at all.
    Agreed. But the program I'm working on reads a random-access file at several points, and a few of these should be in separate functions. It seems more reasonable to make the FILE * a global variable rather than always passing it as an argument.

  4. #4
    Registered User
    Join Date
    May 2010
    Posts
    4,630
    It seems more reasonable to make the FILE * a global variable rather than always passing it as an argument.
    No, not really. Passing it by a parameter gives you more control of the variable. When passed by a parameter you should be less inclined to close the file in one of the functions, which would cause problems, especially if you properly close the file before the variable goes out of scope.

  5. #5
    Registered User
    Join Date
    Apr 2020
    Location
    Greater Philadelphia
    Posts
    26
    Quote Originally Posted by jimblumberg View Post
    No, not really. Passing it by a parameter gives you more control of the variable. When passed by a parameter you should be less inclined to close the file in one of the functions, which would cause problems, especially if you properly close the file before the variable goes out of scope.
    Thanks, I'm following your advice in this case.

    But here's another:

    1. Main() uses malloc() to allocate a block of memory.

    2. A function other than main() calls exit().

    3. Exit() closes files and calls any functions registered via atexit()
    but, according to the documentation I've seen, it does not free malloc's memory blocks. We need a function registered by atexit() to do this, and such a function must be void.

    What alternative do we have to a global variable for the pointer to such memory?

  6. #6
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,401
    The function that called exit() should have freed the memory. That it is indiscriminately calling exit() without doing such cleanup is either a bug, or more likely the programmer is relying on the fact that in a modern OS, any remaining allocated memory will be released after the process exits.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  7. #7
    Registered User
    Join Date
    Apr 2020
    Location
    Greater Philadelphia
    Posts
    26
    And I was hoping to use exit() to simplify the challenge of file I/O errors which should be rare but must always be anticipated. If we must preclude registering a function to free memory at exit, simply because we don't want a global variable, it hardly simplifies anything.

  8. #8
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,401
    Quote Originally Posted by Alogon
    And I was hoping to use exit() to simplify the challenge of file I/O errors which should be rare but must always be anticipated.
    Are such errors really so catastrophic that they cannot be handled gracefully? If they are so catastrophic that calling exit() is the right thing to do, are you programming for a system that doesn't have an OS that will reclaim the memory after the process has ended?
    Last edited by laserlight; 04-11-2020 at 01:45 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  9. #9
    Registered User
    Join Date
    Apr 2020
    Location
    Greater Philadelphia
    Posts
    26
    Quote Originally Posted by laserlight View Post
    Are such errors really so catastrophic that they cannot be handled gracefully? If they are so catastrophic that calling exit() is the right thing to do, are you programming for a system that doesn't have an OS that will reclaim the memory after the process has ended?
    If a file has been properly opened and then corrupt data interferes with further I/O in the same file or another file, what would constitute graceful handling? The program needs to provide as informative a message as possible, clean up after itself (which certainly should include closing files, which exit() does, and freeing allocated memory, which it can do if it calls a function to do it), and quit. What I'm hoping is that it needn't mean function level 6, which detects the error, telling function 5 that there is an error, which tells function 4 that there is an error, etc. all the way back up to main(), which finally displays the error message. This is likely to require a degree of error-handling logic that obscures what each function is trying to do in the first place. I've been there, and if there are any shortcuts for such situations, I'm eager to learn about them. Aren't exit() and atexit() designed to help?

  10. #10
    null pointer Structure's Avatar
    Join Date
    May 2019
    Posts
    338
    Using a prototype and putting the function after main() is useful when you have a "global" defined in main that you want to access in the function. Similar to scope, when the compiler tries to access a variable that is not defined it should throw an error.

  11. #11
    Registered User Sir Galahad's Avatar
    Join Date
    Nov 2016
    Location
    The Round Table
    Posts
    277
    Quote Originally Posted by Alogon View Post
    Thanks, I'm following your advice in this case.

    But here's another:

    1. Main() uses malloc() to allocate a block of memory.

    2. A function other than main() calls exit().

    3. Exit() closes files and calls any functions registered via atexit()
    but, according to the documentation I've seen, it does not free malloc's memory blocks. We need a function registered by atexit() to do this, and such a function must be void.

    What alternative do we have to a global variable for the pointer to such memory?
    You can also use setjmp/longjmp, although you do have to be REALLY careful with them (lest your program fall into in an "invalid" state).

    Code:
    #include<stdio.h>
    #include <setjmp.h>
    
    jmp_buf handler;
    
    enum
    {
     success = 0, 
     error = 1
    };
    
    void this_might_fail(void)
    {
     if(rand() & 1)
      longjmp(handler, error);
    }
    
    int main()
    {
     puts("- Iffy -");
     char* data = malloc(1024);
     int status = setjmp(handler); 
     if(status == error)
     {
      puts("Something failed!");
      free(data);
      return error;
     }
     srand(time(NULL));
     this_might_fail();
     free(data); 
     puts("Success!");
     return success;
    }

  12. #12
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,401
    Quote Originally Posted by Alogon
    If a file has been properly opened and then corrupt data interferes with further I/O in the same file or another file, what would constitute graceful handling?
    It is still reasonable to have the input processing function return an error code so that the caller can handle it, and at each point there will be a caller that has enough information to know that certain resources need to be freed, after all of which exit() can be called to do the rest of the cleanup and end.

    But you do raise the next point...

    Quote Originally Posted by Alogon
    What I'm hoping is that it needn't mean function level 6, which detects the error, telling function 5 that there is an error, which tells function 4 that there is an error, etc. all the way back up to main(), which finally displays the error message. This is likely to require a degree of error-handling logic that obscures what each function is trying to do in the first place.
    I agree, and I think that is a legitimate criticism of error handling in C (and newer languages like Go that copied the approach). On the other hand, the case can be made that it is a very explicit and hence knowable way of doing things, unlike say, calling a function and not realising that it could propagate an exception. I believe this latter criticism also applies to approaches like using setjmp and longjmp.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  13. #13
    Registered User
    Join Date
    Apr 2020
    Location
    Greater Philadelphia
    Posts
    26
    Quote Originally Posted by laserlight View Post
    It is still reasonable to have the input processing function return an error code so that the caller can handle it, and at each point there will be a caller that has enough information to know that certain resources need to be freed, after all of which exit() can be called to do the rest of the cleanup and end.
    Over the last few hours, I've studied what a few sites have to say about error handling in C. They all begin by saying that error handling facilities are very limited, almost non-existent. There are explanations of setjmp()-longjmp(), but hardly anyone is fond of that method. The caveats can be frightening.

    So maybe what I've been doing is as good as anything: establishing my own enum type of error codes called erc, paired with error messages explaining them, writing functions that return an erc, and letting them propagate up as needed. Most of these functions must place their normal output into memory per a pointer passed by the caller, anyway. With success, erc=0, which is a little counterintuitive, but it allows for a host of other informative values. Nothing I do as a hobbyist is very complex compared to projects requiring teams of professionals, so I can live with it.

    Thanks to everyone for your thoughts!

  14. #14
    Registered User
    Join Date
    Apr 2020
    Posts
    7
    That seems about right. I usually put my typedefs just above the structures themselves unless the structure itself is a typedef.

    I suppose everyone has different taste. Although I also put the function return type and typedefs on their own line for readability:

    Code:
    typedef struct
    struct_type {
            /* fields */
    } Struct_Type, *Struct_Type_POINTER;
    It helps keep the structure or function name in the first column since return types vary in length.
    Last edited by TheMuffenMann; 04-19-2020 at 12:57 PM.

  15. #15
    TEIAM - problem solved
    Join Date
    Apr 2012
    Location
    Melbourne Australia
    Posts
    1,907
    Have a look at
    Code:
    #include <errno.h>
    You can also use a custom typedef to handle error - I like this because it allows me to slowly "throw" the error back to a function that could do something about it.

    The classic one is when a malloc fails and a novice programmer would puts an exit there spitting out a message like "malloc fail".

    Now imagine that you are a user who has spent 2 hours putting sales figures (for example) into the program. Behind the scene one of the malloc calls for a new "employee" struct fails. What does the user then see?...

    "malloc fail", and then the program closes. 2 hours of work gone, along with any faith that they have in your program. You can expect an angry/panic phone call asking how to get the data back. If there is another program that does the same thing, you can bet that they'll start looking at prices.

    With this very simple example (I'm just typing this on the fly, excuse any errors)
    Code:
    typedef int iResult;
    
    #define NO_ERROR 0
    #define MALLOC_FAIL 1
    #define INVALID_ARGUMENT 2
    Code:
    iResult getMemory(struct fruitStruct **fruit)
    {
        *fruit = malloc(sizeof(**fruit));
    
        if (fruit == NULL)
            return MALLOC_FAIL;
    
        return NO_ERROR;
    }
    Code:
        do
        {
            switch( getMemory( &fruitBasket ))
            {
            case MALLOC_FAIL:
                perror("Memory allocation error - Would you like to try closing other programs and try again? (Y/N)");
                userFeedback = getUserFeedback();
                break;
            }
        }
        while (userFeedback == 'Y');
    This by itself is a very useless example, but you can see if I was to start adding other things, like initialising values and adding the INVALID_ARGUMENT how I get control of the program when dealing with errors.

    You could start adding error handling for other issues such as OVERFLOW and UNDERFLOW and predicting problems before any data gets smashed.

    What if the user says that they don't want to try again - What do you want it to do then? Once again, don't suddenly close the program, start thinking of ways to recover.
    Fact - Beethoven wrote his first symphony in C

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 14
    Last Post: 01-28-2020, 08:07 PM
  2. best practice on RAII file loading
    By jiggunjer in forum C++ Programming
    Replies: 7
    Last Post: 07-07-2015, 04:27 PM
  3. Save File Organization Question
    By Zafaron in forum C++ Programming
    Replies: 25
    Last Post: 05-12-2011, 03:48 AM
  4. newbie question reguarding safe practice of file io
    By Ashii in forum C++ Programming
    Replies: 2
    Last Post: 11-05-2008, 03:47 PM
  5. Multi file source to single file source
    By anonytmouse in forum Tech Board
    Replies: 4
    Last Post: 12-07-2003, 08:47 AM

Tags for this Thread