Structures and Functions in C

We have seen that a data structure can be used to define a new data type. Starting from this we can use this new data type to pass parameters to a function in the form of data structures.

Using data structures in this way is useful in many cases where logically correlated information needs to be passed to a function.

Furthermore, we will see how to return a data structure as a return value of a function.

Structures as Function Arguments

In the previous lesson we saw how to define structures as data types.

Since a data type can be a struct, we can use such type as a function argument.

For example, we can define a data structure that represents a point in a Cartesian plane:

struct point {
    int x;
    int y;
};

and define a function that accepts as argument a variable of type struct point. We can define a function that calculates the Euclidean distance of the point from the origin of the plane:

#include <math.h>

double distance(struct point p) {
    return sqrt(p.x * p.x + p.y * p.y);
}

In this example we used a tagged structure. We could also have defined a new type through typedef:

typedef struct {
    double x;
    double y;
} point;

double distance(point p) {
    return sqrt(p.x * p.x + p.y * p.y);
}

The main thing to keep in mind when using struct as function argument is that, when passing a variable of type struct as argument it will always be passed by copy. In other words, if we invoke the distance function in this way:

struct point x = { 3.0, 4.0 };

double d = distance(x);

the variable x will be copied into the variable p inside the distance function.

As long as we are dealing with data structures of small dimensions, this is not a problem. However, if the structure starts to be of large dimensions the pass by copy can be burdensome. This issue is kept hidden by the compiler, however when trying to optimize a program it must be taken into account.

For example, suppose we have a data structure of this type:

struct vector {
    float elements[1024];
};

This structure is composed of as many as 1024 elements of type float. Let's try to create a function that calculates, for example, the average of all elements of the structure:

float average(struct vector v) {
    float sum = 0.0;
    for (int i = 0; i < 1024; i++) {
        sum += v.elements[i];
    }
    return sum / 1024.0F;
}

As it is defined, this function copies the elements of the structure every time it is invoked. This means that each invocation involves copying 1024 float elements from one memory point to another. This is a very high cost, especially if the function is invoked many times.

For this reason, passing data structures as arguments to a function should be used sparingly. In other cases, as we will see in the future, it is better to pass data structures to a function by reference. To do this, however, we must first study in a future lesson the pointers to struct.

Definition

Passing struct to function

A function in C language can accept as input parameters of data structure type, struct.

The syntaxes for specifying parameters of type struct are:

  • Tagged struct:

    struct struct_name {
        /* ... */
    };
    
    return_type function(struct struct_name parameter_1, struct struct_name parameter_2) {
        /* ... */
    }
    
  • typedef:

    typedef struct {
        /* ... */
    } struct_name;
    
    return_type function(struct_name parameter_1, struct_name parameter_2) {
        /* ... */
    }
    

When passing an argument of type struct to a function it will always be passed by copy. This means that the function will receive a copy of the structure's data, and not a reference to the structure itself.

Structures as Function Return Values

In this lesson we have seen how to pass a data structure as a function argument. We can do the same also for return values of a function.

For example, given the structure that represents a point in a Cartesian plane:

struct point {
    double x;
    double y;
};

we can define a function that calculates the midpoint between two points:

struct point midpoint(struct point p1, struct point p2) {
    struct point p;
    p.x = (p1.x + p2.x) / 2.0;
    p.y = (p1.y + p2.y) / 2.0;
    return p;
}

In this example, the function first creates the variable p that represents our return value. Subsequently, it calculates the values of x and y and assigns them to the variable p. Finally, the function returns the value of p.

In returning the struct variable p, the function copies all the data of the structure. So, for example, if we invoke the function in this way:

struct point p1 = { 1.0, 2.0 };
struct point p2 = { 3.0, 4.0 };

struct point x = midpoint(p1, p2);

what happens is that the result of the function will be copied into x. Therefore, the same performance considerations apply to the return value of a function as well. If the structure is of large dimensions returning a value of that type can be very burdensome.

In Summary

In this lesson we studied the passing of a data structure as a function argument. We saw that in general the passing happens by copy.

Furthermore, we saw how to pass a data structure as a return value of a function. Also in this case, the return happens by copy.

Such passing can be very burdensome in cases where the data structure to be passed is of large dimensions. In these cases, it is better to pass the data structure by reference. To do this, however, we must first study the pointers to struct as we will see in future lessons.