 
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: April 30, 2025
When we write programs in the C language, especially on Linux, we often split our code into multiple source files. This splitting helps us manage and organize our program files. But what happens when we want to share a struct we defined in one source file with another? Unsurprisingly, this is a common scenario, and if we don’t handle it correctly, we may run into compiler errors or messy code duplication.
In this tutorial, we’ll look at how we can use a struct defined in one C source file in another. We’ll focus on the solutions, how they’re implemented, and how they compare to each other.
Suppose we have a simple struct definition in one C source file called main.c:
$ cat main.c
#include <stdio.h>
// Local struct definition
struct test_st {
    int state;
    int status;
};
// create an instance of the struct structure
struct test_st instance = {.state = 5, .status = 10};
//function to print a struct attribute
void print_main() {
    printf("Main: state=%d\n", instance.state);
}
int main() {
    print_main();
    external_function();  // Defined in othersrc.c
    return 0;
}This is a simple source file that defines our struct, creates an instance of it, and calls a function to print the struct. Notably, we also want to execute an external function in another C file.
Now, let’s say we want to use this test_st struct in another C file, such as othersrc.c:
$ cat othersrc.c
#include <stdio.h>
// Attempt to use struct without definition
void external_function() {
    struct test_st copy;  // ERROR: incomplete type
    copy.state = 20;      // Won't compile
    printf("Other: %d\n", copy.state);
}Let’s try to compile these two files:
$ gcc main.c othersrc.c -o brokenThis code uses gcc to create an executable named broken. When we compile this, an error is displayed in the output:
othersrc.c: In function 'external_function':
othersrc.c:5:18: error: storage size of 'copy' isn't known
    5 |     struct test_st copy;
      |                  ^~~~The output shows that our compiler doesn’t recognize the struct, as it doesn’t know where to find the definition. A straightforward solution is to copy the struct definition into the other C source file. Although this idea might seem quick, it’s inefficient.
Let’s imagine we had hundreds of files that use the test_st struct. If we change its attributes later, we’d have to go into these different files to change its copy-pasted definition, which is a recipe for bugs.
How then do we share our struct definition for clean and efficient code without copying it into the new source file?
One of the most common and reliable ways to do this is to use a header file. Generally, this method is recommended as best practice since defining the struct in a header file enables us to use it across multiple files:
$ cat main.h
#ifndef MAIN_H
#define MAIN_H
struct test_st {
    int state;
    int status;
};
extern struct test_st instance; // Declaration
#endifThen, we can access this definition in other files using the include statement. Let’s modify the othersrc.c file:
$ cat othersrc.c
#include <stdio.h>
#include "main.h"
void external_function() {
    printf("Other: status=%d\n", instance.status);  // Full access
    instance.state = 15;  // Modifies shared instance
}Here, we modified the code to include the header file called main.h. This inclusion allows the compiler to link the struct definition and our functions during the compilation process.
The main.c file also changes:
$ cat main.c
#include <stdio.h>
#include "main.h"
struct test_st instance = {.state = 5, .status = 10};
void print_main() {
    printf("Main: state=%d\n", instance.state);
}
int main() {
    print_main();
    external_function();
    return 0;
}Let’s compile again by running the same command, this time creating a new executable:
$ gcc main.c othersrc.c -o working
$ ./working
After compiling our program and checking out the output, we see that the compiler now recognizes the struct:
Main: state=5
Other: status=10
Additionally, we were able to do this without duplicating the struct definition in both files. That’s simple and efficient! Here’s why this method works so well:
Another solution is to use pointers. Sometimes, we want to avoid exposing the full struct definition in other files. An example is when we want to hide implementation details or reduce dependencies.
In such cases, we can use a forward declaration and pointers. Let’s see what this looks like in code.
In the othersrc.c file, we declare the struct without defining it:
$ cat othersrc.c
#include <stdio.h>
// Forward declaration
struct test_st;
// External function declarations
extern void get_struct_ptr(struct test_st **);
extern int get_state(struct test_st *ptr);
extern void set_state(struct test_st *ptr, int new_state);
void external_function() {
    struct test_st *ptr;
    get_struct_ptr(&ptr);
    int current_state = get_state(ptr);
    printf("Other: current state = %d\n", current_state);
    set_state(ptr, 25);
    printf("Other: state updated\n");
}In this code, we have a forward declaration of the test_st struct. This forward declaration tells the compiler that a struct named test_st exists, but its attributes are unknown at this point. To access and work with the struct‘s data, we need functions that operate on pointers declared in our main.c file:
$ cat main.c
#include <stdio.h>
struct test_st {
    int state;
    int status;
};
struct test_st instance = { .state = 5, .status = 10 };
void get_struct_ptr(struct test_st **pptr) {
    *pptr = &instance;
}
int get_state(struct test_st *ptr) {
    return ptr->state;
}
void set_state(struct test_st *ptr, int new_state) {
    ptr->state = new_state;
}
int main() {
    printf("Main: initial state=%d\n", instance.state);
    external_function();
    printf("Main: modified state=%d\n", instance.state);
    return 0;
}Here, we’ve defined the struct and the functions that work based on the pointers. Let’s compile the program and see the result:
$ gcc main.c othersrc.c -o opaque
$ ./opaque
Main: initial state=5
Other: current state = 5
Other: state updated
Main: modified state=25The results show that the othersrc.c file can now access the defined struct in main.c and modify its attributes using pointers. This approach is suitable when working with large projects to reduce recompilation time. The downside is that it’s more complex to maintain.
Although both of these methods can help us achieve our goal, we have cases where they’re used ideally:
| Aspect | Header File Method | Pointer Method | 
|---|---|---|
| Access to struct Members | Direct access to struct members (e.g., struct_name.struct_member) | Indirect access via functions | 
| Ease of Use | Simple and straightforward | Requires extra accessor functions | 
| Code Maintenance | Centralized struct definition | More modular, encapsulates data | 
| Compilation | Requires recompilation if the header changes | Reduces recompilation in some cases | 
| Use Case | More general-purpose projects | Ideal for building libraries that need encapsulation | 
At a glance, the header file method provides more organization and is ideal for simple, straightforward projects. Conversely, the pointer method is ideal for projects that require some level of encapsulation and privacy.
In this article, we discussed two ways to share struct information between source files in C. The header file approach provides a clear, concise solution, whereas the pointer-based method is suited for instances where we don’t want to expose implementation details. We also considered the trade-offs in areas of compilation, code maintenance, and ease of use.