2

I had to write a C program with more than 600 lines and about 25 functions. This is the longest C code that I've written.

I noticed that some of the functions have more than 5 arguments. The ones that are directly called from main() have more arguments. The further away it goes from main(), the fewer.

I also noticed that I frequently had to pass an argument to a function, not because that function directly uses that argument, but the function calls another function that needs that argument.

So it would look something like

void f1(int a, int b,..., int bar){
    int foo = f2(bar); // the only time 'bar' is used in f1
    .
    .
    .
}

I tried to minimize the use of global variables, but I had to declare some global variable because some arguments became way too redundant. Basically I had to pass those arguments to every function.

I'm not an experienced programmer but when I programmed in Java, I don't think I ever needed to write a method with more than 5 arguments.

Is it normal in C to pass way more arguments than in other languages? Is it just the nature of Procedural Programming vs OOP? Or am I just writing bad C code?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Matt Yoon
  • 396
  • 4
  • 11
  • 2
    You can pass a `struct` if you want your function signature/call to be more concise. – Fiddling Bits Jun 16 '20 at 20:11
  • 2
    No, just some (many :)) languages take the 'object' parameter implicitly, i.e. you do not specify it explicitly within your code, but it's still there. If you organize your data in structures (so you can pass some arguments together), you shouldn't see a large (any) difference. – Al.G. Jun 16 '20 at 20:12
  • @FiddlingBits Oh, I can see how using `struct` will help. Thank you. – Matt Yoon Jun 16 '20 at 20:25
  • @Al.G. Okay, thank you. If I'm correct the only data structure that can do that in C is `struct`? I will try using `struct` more frequently. – Matt Yoon Jun 16 '20 at 20:26
  • 1
    By the way, you have used the ellipsis symbol: `...`, which in C (and in C++) is significant in that your second argument makes this a [variadic function](https://en.cppreference.com/w/c/variadic) prototype, allowing from one, to many repeating arguments of the same type. In the context of your question of course this is not what you meant to convey. :) – ryyker Jun 16 '20 at 20:38

3 Answers3

5

Your concerns about avoiding too many global variables are correct, because with them programs tend to become unmaintainable, especially when they grow in size. So let's pass data to functions through parameters.

Using parameters to provide your functions with the information they need to execute is a good idea for many reasons. It offers first of all the readability of the code, but also make it easier writing thread safe functions (functions that can be called safely from different threads).

Anyway, generally speaking, defining a function with too many parameters sometimes may indeed cause inefficiency in terms of stack consumption (especially in embedded systems, where only the first 3 or 4 parameters use the processor registers, with all the others parameters copied to the stack).

The solution is using data structures. With structs (not so different from what, in OOP languages such as Java, you would call objects) you can group together your global data in logical entities. This makes possible to define of all your data together, to change its fields, and to pass it to your functions. And passing it by pointer makes possible modifying it in its original location.

So your example functions would become

typedef struct
{
    int a;
    int b;
    int bar;
} MyData_t;

void f1( MyData_t *data )
{
    int foo = f2( data->bar );

    /* ... */
}

int main( void )
{
    MyData_t d = { 5, 7, 9 };

    f1( &d );

    return 0;
}

You need to change f2() passing it more parameters? Well, forward the pointer to MyData_t to it and you'll access all the variables you need.


In conclusion, to answer your question, probably a statistical analysis would result in less parameters passed in OOP languages rather than procedural languages: just because in most of them the calling object would be a parameter in a procedural language. But with well designed structured programming, this difference would be very small.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Roberto Caboni
  • 7,252
  • 10
  • 25
  • 39
  • Nice answer. I'm thinking about adding my thoughts to your answer or writing an own. – klutt Jun 16 '20 at 21:27
  • @klutt thanks, I appreciate it. Talking about your answer, if your thoughs are enough to build a complementary answer I don't see a reason to retain it. But if the difference are actually minor (and/or you want to correct my poor grammar, especially when I have to deal with 'data' that's a word I always make a mess with when it comes to choose plural or singular form) feel free to edit it. ;) I hope for the answer option, anyway. – Roberto Caboni Jun 16 '20 at 21:39
  • I chose my own answer. Thought I had a few things to add that would not feel good to make it as if you said them. – klutt Jun 16 '20 at 21:49
  • "Structured programming" doesn't refer to structs. It refers to the use of control flow constructs like `if`, `while`, and `for`, in contrast to the mess of spaghetti GOTOs that was popular before. – user2357112 Jun 16 '20 at 21:52
  • @user2357112supportsMonica I'll check that term, but in the meantime I reworded it. It was not _so_ important in my answer's economic using _those_ terms (especially if they are not accurate ;) ) – Roberto Caboni Jun 16 '20 at 22:13
3

Is it normal in C to pass way more arguments than in other languages? Is it just the nature of Procedural Programming vs OOP? Or am I just writing bad C codes?

It's a very common thing. It's because in C, we don't have objects (yes, we do, but it's something completely different than what Java people would call objects) but instead just variables (objects in C).

So you're passing the equivalent of a class to C functions by just passing every single attribute in that class to the function. A function inverting matricies would have this signature:

void inverse (const double *input, size_t ix, size iy, 
              double *output, size_t ox, size_t oy);

In Java or C++, it would look like something like this:

void inverse(const Matrix &input, Matrix &output);

(I'm not a good C++ coder so forgive me for mistakes)

The point is that the Matrix object contains the dimensional inside them member variables. Something that is frowned upon in OOP-languages is dataclasses, which is classes without methods and public member variables.

There is an equivalent for that in C, and that is a struct. No support for member functions, unless you're using function pointers (You can do OOP in C, but it's very messy). What a struct basically is, is just a class without encapsulation and support for private members. So they suit the purpose.

Unlike arrays, you don't have to pass them as pointers. This is perfectly fine:

struct myStruct {
    int a;
    char b;
    double c[2];
    void **;
};

bool intIsFortyTwo(struct myStruct args) {
    return args.a == 42;
}

You can also return structs:

struct myStruct initStruct() {
    struct myStruct ret = {.a=22, .b='a'};
    return ret;
}

In general, pointers are advisable, for performance reasons. If you don't use pointers, a whole copy of the struct will be created. The first function could then be

bool intIsFortyTwo(struct myStruct *args) {
    return args->a == 42;
}

Passing structs to functions is not extremely common, but it's not strange either. Use it you find it useful.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
klutt
  • 30,332
  • 17
  • 55
  • 95
1

To expand on the point made in comments, efficiency of passing a single pointer to struct when the alternative is a large number individual of arguments is improved simply because the sizeof a single pointer is passed for as many members as you like (as long as it is less than 1023) is small when compared to passing a number of pointers, or values representing individual arguments. It is also much easier to read one argument as opposed to anything with more than 3 or 4 arguments.

So, even with your example of a reletively small prototype: void f1(int a, int b, int bar) (having removed the ellipsis.) and given 32 bit target, with 4bytes/int

sizeof a + sizeof b + sizeof bar == 12bytes

Compared to the size of a pointer to a struct with over 1000 members

typedef struct {
    int a;
    int b;
    ... say 1000 more ints
    int bar;
}data_t;

data_t *pData 
pData = &data_t;//point pointer back to location t_data.

sizeof pData == 8 bytes
ryyker
  • 22,849
  • 3
  • 43
  • 87