-3

I am trying to figure out to split a program into multiple files to have 1 .h file and 2 .c files. I have a full program but just for this example, I would like the function of printing of the output to be in a separate .c file. I know how to make a function of basic arithmetic like:

int Sum(int a, int b) 
{ 
    return a+b; 
}

but how would I make a function with a for loop and just the code below?

for (i = 0; i < count; i++)
{
    printf("\n%s", ptr[i]->name);
    printf("%s", ptr[i]->street);
    printf("%s", ptr[i]->citystate);
    printf("%s", ptr[i]->zip);
    free(ptr[i]);    
}

I get the way it works is like this, just don't know how to make a for loop into a function.

Functions.h:

#ifndef FUNCTIONS_H_INCLUDED
#define FUNCTIONS_H_INCLUDED
/* ^^ these are the include guards */

/* Prototypes for the functions */
/* Sums two ints */
int Sum(int a, int b);

#endif

Functions.c:

/* In general it's good to include also the header of the current .c,
   to avoid repeating the prototypes */
#include "Functions.h"

int Sum(int a, int b)
{
    return a+b;
}

Main.c

#include "stdio.h"
/* To use the functions defined in Functions.c I need to #include Functions.h */
#include "Functions.h"

int main(void)
{
    int a, b;
    printf("Insert two numbers: ");
    if(scanf("%d %d", &a, &b)!=2)
    {
        fputs("Invalid input", stderr);
        return 1;
    }
    printf("%d + %d = %d", a, b, Sum(a, b));
    return 0;
 }
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    The problem you are having with that is unclear. Pleae make a [mcve] which is in one .c file and uses the loop above from a function (probably with parameters `int count, mystruct* ptr` at a guess). Then explain what keeps you from splitting the using function into another .c. – Yunnosch May 08 '18 at 05:46
  • 1
    The program you show does not contain a for loop. You are not asking about how to use a for loop in a function, are you? If you are, change the title and the explanation, please. – Yunnosch May 08 '18 at 05:50
  • The for loop is for printing the results, its on the top. The example I showed is just to show i did research. My question is both, I need to do multiple file format with my program, specifically I was going to do it with that for loop function. Just not sure how. – user9593492 May 08 '18 at 05:56
  • 1
    @user9593492 -- you still stuck on this? I thought you were making progress? If you are summing `a+b` in a separate source -- then why are you still dragging the old code from the address sort around? If you have code you are currently working on that shows how the sum relates to the address, post the entire code. It is really difficult for people to help you with only part of your code shown -- this isn't poker -- it's OK to show your hand. – David C. Rankin May 08 '18 at 06:08
  • @DavidC.Rankin Yeah Im trying to seperate the files. Yes the full code is posted there now. – user9593492 May 08 '18 at 22:45
  • @DavidC.Rankin Does it look like im anywhere near correct?https://pastebin.com/NVYCBMJU – user9593492 May 11 '18 at 02:39
  • Give me a minute and I'll drop you another example, either here or I'll provide a link. – David C. Rankin May 11 '18 at 02:40
  • Ok [3 files split](http://paste.opensuse.org/93547087), your primary problem was not understanding you need to declare the struct within the header because you use it in both `function.c` and `main.c` (there are ways to encapsulate, but that is well beyond your struggles at the moment). Your next problem was in your declaration for `output`. Do NOT call it in a loop in main, you are looping in `output`. Additionally, you need `*ptr[MAXC]` not `ptr[MAXC]` to pass the *array-of-pointers-to-address*, instead of *array-of-address*. – David C. Rankin May 11 '18 at 03:37
  • Also, you can remove the `#if defined (_WIN32) || defined (_WIN64)` preprocessor conditionals. I just did that to avoid the `strncpy_s` since I was building on Linux. (they just check if you are compiling on windows, if so, use `strncpy_s` and `system (pause)`, if not, then `strncpy` is used and `system(pause)` is omitted. – David C. Rankin May 11 '18 at 03:43
  • Oh wow, so I was somewhat on the right track but also far away haha. So on the main youre saying I dont need the `#if defined (_WIN32) || defined (_WIN64)` but do I need the `#else` and `#endif` in main? – user9593492 May 11 '18 at 03:50
  • And could a different date type other than `size_t` be used in this situation? – user9593492 May 11 '18 at 03:52
  • No, you don't need any of the `#if`, `#else`, or `#endif` (if you removed the `#if` obviously the other two had to go `:)`. You were not far off. Just think about what each function needs to be able to see, and make sure that is included before you get to that funciton. (any time you are counting, `size_t` should be used, but of course, you can use `int` or anything else as long as it exceeds the range of numbers you will have it use) – David C. Rankin May 11 '18 at 03:53
  • Also, since I/you include `stdio.h` and `stdlib.h` in `header.h`, you can remove those headers from `main.c`. – David C. Rankin May 11 '18 at 03:55
  • Ok so does this look right if I wanted to put all functions in functions.c? https://pastebin.com/AyUnhmC5 – user9593492 May 11 '18 at 04:13
  • Should be fine, but typo `void sort (address *ptr[MAXC, int count)` You are compiling with `/W3` warnings enabled on VS -- right? – David C. Rankin May 11 '18 at 04:16
  • Oh missed that, thank you. yes that is right. I kept getting compiler errors. I guess I needed my variable declarations both on **functions.c** and **main.c** `struct address *ptr[MAXC]; struct address *tptr; char buffer[MAXL]; int count = 0, i, x, y;` – user9593492 May 11 '18 at 04:27
  • So it compiles now and runs but when i paste my addresses and hit CTRL+Z, it doesnt do anything, one thing after another – user9593492 May 11 '18 at 04:31
  • I ran it what I posted and an ran fine and sorted the address (redirecting the address from a file -- which works on windows too...) You only need the variables that are required as parameters to your functions. You should not have to declare any other variables in `function.c`. You pass what is needed as parameters. If you can do it in `main.c`, you can wrap it as a function and move it to `function.c` -- just pay attention to what the function needs as input and pass that as a parameter. – David C. Rankin May 11 '18 at 04:38
  • I ran what you posted and I got a compiler error, did you accidentally add another `strncopy` in the read function ? I deleted that second one to get rid of the compiler error and it doesnt do anything for me – user9593492 May 11 '18 at 05:01
  • This is my latest program right now and runs, no errors but doesnt do anything. https://pastebin.com/Svbs9wDS – user9593492 May 11 '18 at 05:03
  • Of course you get nothing -- `count` is always **zero** back in `main()` -- how are you getting `count` back to `main()`. You have two options. Either pass a *pointer-to-count* as the parameter, so you are updating the value at the same address `counter` has in `main()` or delete `count` as a parameter, declare `count` in `read()` and then `return count;` at the end and assign the return to some variable (could be another `count`) in `main()` – David C. Rankin May 11 '18 at 05:17
  • Now compare with [3 files split 2](http://paste.opensuse.org/99185847) – David C. Rankin May 11 '18 at 05:32
  • @DavidC.Rankin That works! At first it wasn't doing anything. I had to delete all the `#if #else #windows` then it worked after. I also noticed you changed `char buffer[MAXL];` to `char buffer[MAXL] = "";` – user9593492 May 12 '18 at 05:41
  • Good, glad it worked. The `char buffer[MAXL] = "";` simply initializes the buffer to all zero (you should always initialize all variables). It doesn't make a difference here, but it is good habit. Long process, but hopefully you are learning. C has a steep learning curve, but, just like eating a whale... you do it one bite at a time... – David C. Rankin May 12 '18 at 06:53
  • Got it, well you have been extremely helpful, I do really appreciate it, thank you so much. – user9593492 May 12 '18 at 23:45
  • @DavidC.Rankin If I wanted to take this one step further, and use arguments `int main( int argc, char * argv [] )` how hard would that be? – user9593492 May 17 '18 at 02:43
  • It would be simple. You can actually use an either/or setup by using a *ternary* operator to initialize `a` and `b`, e.g. `int a = argc > 1 ? atoi (argv[1]) : 0, b = argc > 2 ? atoi (argv[2]) : 0;` The *ternary* operator works this way `test ? if true value : if false value`. So if you provide the values for `a` and `b` as arguments, then they are converted to `int` using `atoi` (used in this comment, you should use a full `strtol` with error checking instead). If they not given then they are `0` in your code and you can prompt for values. – David C. Rankin May 17 '18 at 04:15
  • @DavidC.Rankin Is that the most efficient way of doing it? – user9593492 May 17 '18 at 04:34
  • You can use `if else` if you like. If you want to use the command line to pass the values -- then yes. Recall, all command line arguments are *strings*, so you must convert the values to `int` somehow. `strtol` is the correct way. (and you need to choose a default value that won't be entered - like `0` or `-1` if they varaibles represent size). If they are just `1-9` values, then you can simply use `*argv[i] - '0'` to convert a single digit. There isn't any "more efficient" way to pass command line params. If you need more flexibility, you can use `getopts` to allow, e.g. `-x -h -o`, etc. – David C. Rankin May 17 '18 at 04:52
  • Yes the intent is to use the command line and call the arguments, Ill try to come up with something. – user9593492 May 18 '18 at 03:50
  • @DavidC.Rankin So in my case it would have 4 `argv` including the *.exe file*, the *input file* which im reading the addresses, and the *output file* which is the sorted list and last is the `NULL` – user9593492 May 18 '18 at 04:18
  • Yes. `argv[0]` is always the `exe` name. Then your command line arguments `1` to `argc - 1` (where the last entry in your *argument vector* at the index of your *argument count* (`argv[argc]`) is always `NULL`) – David C. Rankin May 18 '18 at 05:01
  • @DavidC.Rankin would it be something as easy as replacing my current functions.c *read* function to something like`if (fgets(buffer, MAXL, argv[1]) == NULL)` for the input? – user9593492 May 19 '18 at 04:24
  • It (if your code is still similar to my last pastebin) would as easy as adding `FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;` in main() and changing `read` to `int read (address *ptr[MAXC], FILE *fp)`, passing `fp` to `read` and in read changing `if (fgets(buffer, MAXL, fp) == NULL)` – David C. Rankin May 19 '18 at 05:30
  • That can't be it, can it? So if in the command line i wrote `code.exe input.txt output.txt`, that only change in `read` will do the job? I would have thought somewhere in the code, we have to identify`arg[1]` and `arg[2]` ? – user9593492 May 19 '18 at 05:41
  • And are you saying we'll add `FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;` under `int main(int argc, char * argv[])` or it would be `int main(FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;)`? – user9593492 May 19 '18 at 05:48
  • @DavidCRankin. Forgot to tag your name – user9593492 May 20 '18 at 05:22
  • @DavidC.Rankin hows this ? https://pastebin.com/xWAVNSm6 – user9593492 May 21 '18 at 04:53
  • It's not far off, but it will not compile. There are some fundamental errors. You are not passing the correct number of arguments to either `read` or `output` in `main()`. If you are using `fpout`, then it should be `FILE *fpout = argc > 2 ? fopen(argv[2], "w") : stdout;` (you have both `fpin` and `fpout` pointing to `stdin` when no arguments are given. Your `output` function is type `int` but fails to return a value. At least `return 0;`. Here is a secret - See [**How to debug small programs**](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/) -- it will help. No guessing in C – David C. Rankin May 21 '18 at 05:51
  • @DavidC.Rankin That looks very helpful! Thanks, ill take a read. So I changed `output` to `void`. but on `FILE *fpin = argc > 2 ? fopen_s(argv[1], "r") : stdin; FILE *fpout = argc > 2 ? fopen_s(argv[2], "w") : stdout;` and on `count = read(ptr);` im getting an error *too few arguments for call* – user9593492 May 21 '18 at 21:31
  • @DavidC.Rankin Yea that article was helpful. I got the program compiling now but when i run it on the command line it gives me a runtime error. On the command line is `code. exe input.txt output.txt` just seperated by a single white space? This is my updated program. https://pastebin.com/ueCg3NiS – user9593492 May 24 '18 at 03:49
  • First, get rid of the *ternary* in the `FILE*` declarations -- you always seem to mess it up. It should be `FILE *fpin = argc > 1 ?` not `2`, which should be apparent if you see both `FILE*` statements with a `2` in them -- that's just wrong. Other than than -- it looks GREAT! and it works (I used a simple `fpout` of `"output.txt"` and used `fopen` instead of `fopen_s` -- I'm on Linux). But my addresses output are sorted by zip-code, just as they should be. Either fix your use of the *ternary* or remove them. – David C. Rankin May 24 '18 at 04:34
  • @DavidC.Rankin Well thats great news. Can I see what your code looks like? I keep getting that runtime error from the command line. Did you already have a blank output file name `output.txt` in the C drive? – user9593492 May 25 '18 at 01:40
  • Sure: http://paste.opensuse.org/81726222 Note, I just use `stdin` for input, but your `fopen_s` will work fine as long as you have the parameters correct (you don't, see https://msdn.microsoft.com/en-us/library/z5hh6ee9.aspx and note `FILE**`). (input and output files included in paste) – David C. Rankin May 25 '18 at 02:48
  • @user9593492 - OK, I moved my build to Win7 and used the `_s` functions. (1) you cannot use `read` as a function name -- there is already a `read` in the standard library -- are you not reading your warning and fixing them all before running your code? (2) you cannot use a *ternary* with `stdin` and `stdout` when using `fopen_s`. (2a) you CAN use a *ternary* to provide default filenames. See here: http://paste.opensuse.org/89794938 If I teach you nothing else, always compile with `/W3` warnings and do not accept code until it compiles without warning. Now after you copy the code, open... – David C. Rankin May 25 '18 at 03:35
  • Your VS developer command line, change to the directory containing your source files, and enter: `cl /nologo /W3 /Ox /Fesortaddresses /TC main.c functions.c` and then run `sortaddresses.exe your_input_file.txt your_output_file.txt` and confirm whatever name you used as `your_output_file.txt` contains the desired output. – David C. Rankin May 25 '18 at 03:37
  • @DavidC.Rankin I never got a warning about `read`, that wasn't an issue with my compiler, the code is working now and I still just used `read` not `_read`. I also didnt have to change `struct address *ptr[MAXC];` to `address *ptr[MAXC] = { NULL };`. I just had to add those `if` statements. – user9593492 May 26 '18 at 17:08
  • Sorry, you missed the point, you *can* replace `read` with your own function, but *don't do it*. You are just asking for problems as your code grows. Surely you are creative enough to find a name that doesn't conflict with a standard C-library function. Glad you got it working! You should have learned that there is no magic to it, but there are no shortcuts either. Understand what every character in every line of code does, and if you don't, go research it until you do. When you understand each character in each line, you will no longer have any problems. – David C. Rankin May 27 '18 at 00:31

2 Answers2

3

There are two different issues involved (I recommend to address the first one, if so needed, before the second one):

  • how to split a monolithic translation unit in several ones, but keeping the same functions

  • how to refactor a code to make it more readable and made of "smaller" and "better" functions. In your case, this is the main issue.


The first question, for example splitting a small single program in a single myprog.c file of a few dozen thousands of lines, is quite easy. The real issue is to organize that cleverly (and then it becomes harder, and opinion based). You just need to put mostly declarations in your header file, and to put definitions in several translation units, and of course to improve your build process to use and link them together. So you would have first a single common header file myheader.h declaring your types, macros, functions, global variables. You would also define some short static inline functions there, if you need them. Then you would have several C files (technically translation units) foo.c, bar.c, dingo.c, and you'll better put several related functions in each of them. Each such C file has #include "myheader.h". You'll better use some build automation tool, probably GNU make (or something else, e.g. ninja) that you would configure with your Makefile. You could later have several header files, but for a small project of only several dozen thousands of source code lines that might be not needed. In some cases, you would generate some C file from a higher-level description (e.g. use simple metaprogramming techniques).

The second question (code refactoring) is really difficult, and has no simple universal answer. It really depends of the project. A simple (and very debatable, and over-simplifying) rule of thumb is that you need to have functions "doing only one thing" and of at most a few dozen lines each. So as soon as a function does more than one thing or has more than one or two dozen lines you should consider splitting and refactoring it (but you won't always do that). Obviously your main should be split in several stuff.


At last, don't fail into the excessive habit of putting only one function per *.c file, or have lots of small *.c files of only a hundred lines each. This is generally useless, and could increase your build time (because the preprocessor would work a lot), and perhaps even slightly decrease the runtime performance of your executable (because your optimizing compiler won't be able to inline, unless you use link-time optimization). My recommendation (opinion-based, so debatable) is to have source files of several thousand lines each containing several (dozen of) functions.

In your case, I believe your program (in your question) is so tiny that you don't need to split it into several translation units (unless your teacher asks you to). But indeed you need to refactor it, perhaps defining some abstract data type and routines supporting it (see this).


Study the source code of existing free software (e.g. on github) related to your project for inspiration, since you'll need to define and follow many coding conventions (and coding rules) which matter a lot with C programming. In your newbie case, I believe that studying the source of any small free software program (of a few dozen thousand lines, see this) -in a domain you are understanding or interested in, and in the programming language you are practicing- will profit you a lot.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
2

At the simplest, you'd convert the loop fragment into:

void print_and_destroy(size_t count, SomeType ptr[count])
{
    for (size_t i = 0; i < count; i++)
    {
        printf("\n%s", ptr[i]->name);
        printf("%s", ptr[i]->street);
        printf("%s", ptr[i]->citystate);
        printf("%s", ptr[i]->zip);
        free(ptr[i]);
    }
    free(ptr);
}

The final free is there because the array is now contains no useful pointers (they've all been freed). You could add ptr[i] = NULL; after the free(ptr[i]); instead. That indicates that there is no data there any more.

However, as noted in the comments 1 and 2 by SergeyA, and the clumsy but accurate function name, this isn't a good breakdown of the code. You need two functions:

void destroy(size_t count, SomeType ptr[count])
{
    for (size_t i = 0; i < count; i++)
    {
        free(ptr[i]);
        ptr[i] = NULL;  // Option 1
    }
    free(ptr);          // Option 2
}

You would not use option 1 if you use option 2 (though it would do no actual harm). If you do not use option 2, you should use option 1.

void print(size_t count, const SomeType ptr[count])
{
    for (size_t i = 0; i < count; i++)
    {
        printf("%s, ", ptr[i]->name);
        printf("%s, ", ptr[i]->street);
        printf("%s, ", ptr[i]->citystate);
        printf("%s\n", ptr[i]->zip);
    }
}

Note that you probably need space between the fields of the address. You might or might not want one line for name, one line for street address, and one line for city, state and zip code — choose your format to suit. Generally, output newlines at the end of output, not at the beginning unless you want double spacing.


So that would be my separate function.c file and my header file would look something like …code omitted… right? How would the function call in the main program look like?

The outline of the header would be:

#ifndef HEADER_H_INCLUDED
#define HEADER_H_INCLUDED

#include <stddef.h>      // Smallest header that defines size_t

typedef struct SomeType
{
    char name[32];
    char street[32];
    char citystate[32];
    char zip[11];        // Allow for ZIP+4 and terminal null
} SomeType;

extern void print_and_destroy(size_t count, SomeType ptr[count]);

#endif /* HEADER_H_INCLUDED */

Note that the header doesn't include <stdio.h>; no part of the interface depends on anything that's specific to <stdio.h>, such as a FILE * (though <stdio.h> is one of the headers that defines size_t). A header should be minimal, but self-contained and idempotent. Not including <stdio.h> is part of being minimal; including <stddef.h> is part of being self-contained; and the header guards are the key part of being idempotent. It means you can include the header and not have to worry about whether it has already been included before indirectly or is included again later, indirectly, and you don't have to worry about what other headers have to be included — the header is self-contained and deals with that.

In your main(), you'd have something like:

enum { MAX_ADDRESSES = 20 };
SomeType *data[MAX_ADDRESSES];

…memory allocation…
…data loading…

print_and_destroy(MAX_ADDRESSES, data);
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • This is an example of rather bad design. Print and destroy doesn't make a lot of sense when expressed as a function. It would be better to have them as two separate functions, `print` and `destroy`. It is also very counter-intuitive (especially for newbies) that array size plays no role in function signature. Because of that, I always suggest going with pointer, i.e. `SomeType* ptr` – SergeyA May 08 '18 at 05:58
  • @SergeyA: Yes, but I'm encapsulating what was shown. You're right; the print code should be given a `const SomeType` array, which it doesn't modify, and the destroy code should be given the modifiable pointer and a directive to release the memory. – Jonathan Leffler May 08 '18 at 06:00
  • Sure thing, but I just believe mechanical encapsulation here is not showing the best principles, OP would be better helped with non-mechanical illustration of split responsibilities (which is one of the reasons why we use the functions) – SergeyA May 08 '18 at 06:01
  • So that would be my seperate function.c file and my header file would look something like **#ifndef _my_header_file_ #define _my_header_file_ #include void print_and_destroy(size_t count, SomeType ptr[count]) #endif** right? How would the function call in the main program look like? I put my full code here. https://pastebin.com/Kse3nXnL – user9593492 May 08 '18 at 06:06
  • @user9593492: put back-ticks (```…`…`…```) around ``code`` in comments. – Jonathan Leffler May 08 '18 at 06:09
  • @JonathanLeffler if I'm following the right problem, the some of the problem here is due to disjointed fragments of code attempting to be separated into separate source/header files to understand the process from a fundamental standpoint. It does make it rather hard. I'll try and find the prior link. – David C. Rankin May 08 '18 at 06:11
  • So that would be my seperate function.c file and my header file would look something like `#ifndef _my_header_file_ #define _my_header_file_ #include void print_and_destroy(size_t count, SomeType ptr[count]) #endif` right? How would the function call in the main program look like? I put my full code here. https://pastebin.com/Kse3nXnL – user9593492 May 08 '18 at 06:12
  • You are getting it, keep at it. @JonathanLeffler, prior history is [working with pure pointer notation and loops](https://stackoverflow.com/questions/49787334/working-with-pure-pointer-notation-and-loops) -- most of it in comments that was moved to chat after it grew rather lengthy. – David C. Rankin May 08 '18 at 06:15
  • @JonathanLeffler This is my code.https://pastebin.com/Kse3nXnL I saw your update, I thought the .h header file is supposed to be only the function prototype and on a seperate .c file we put the function body? And then just call it in main? – user9593492 May 08 '18 at 06:22
  • 1
    @user9593492: I don't want to go looking at pastebin material. The information should be in the question — sufficient information is in the question, though it could perhaps be better presented and augmented with some, but not all, of what's probably in the pastebin. Please read about how to create an MCVE ([MCVE]). The header should contain the function declaration, but for most practical purposes, it also needs to contain the structure definition as the functions using it will need to know about the type (though you could simply have `typedef struct SomeType SomeType;` in the header). …… – Jonathan Leffler May 08 '18 at 06:25
  • That would be an opaque type, but the code that prints would need to have the details of the structure type, and so would the code in `main()` — or any other function — that loads the data into the structure, or allocates space. It is simplest to include the structure in the header. All the functions referenced outside the source file they're defined in except `main()` should be declared in a header, and the header should be included where the functions are defined and where they are used. Headers are the glue that hold the system together, permitting the compiler to cross-check the code. – Jonathan Leffler May 08 '18 at 06:27
  • @JonathanLeffler Gotcha, I edited my question and posted my code in the bottom. – user9593492 May 08 '18 at 06:30