How Do I Fix Undefined Reference Error When Compiling in Linux?

When I first started compiling C programs in Linux, I ran into one of the most confusing errors I’d ever seen:

undefined reference to `readFile'
collect2: error: ld returned 1 exit status

I had three source filesmain.c, process_info.c, and process_info.h and I was confident I was compiling them correctly. But no matter how many times I ran gcc, I hit the same wall. If you’ve ever been in this situation, let me walk you through exactly what went wrong, how I fixed it, and how I expanded the project to practice Linux system programming.

Pinpointing the Error

At first, I thought the compiler was broken or my commands were wrong. But this was not a compiler error—it was a linker error. The compiler had accepted my code, but during linking it couldn’t find the actual definition of the function readFile.

Looking back at my files, I noticed:

  • In process_info.h I declared: void readFile(char *buffer);
  • In process_info.c I accidentally defined: void readFIle(char *buffer) { ... }

Notice the subtle difference: readFile vs readFIle. That one misplaced capital letter was enough for the linker to fail. In C, function names are case-sensitive, so readFile and readFIle are two completely different functions.

Fix the Code

Once I corrected the spelling so the declaration and definition matched, the program compiled. Along the way, I also cleaned up some smaller issues:

  • Replaced write(WRITE_INDEX, ...) with write(STDOUT_FILENO, ...) to make the output clearer.
  • Added proper close() calls for file descriptors.
  • Made sure the message sizes were consistent.

Here’s the minimal corrected version that finally compiled and worked.

Process_info.h

#ifndef PROCESS_INFO_H
#define PROCESS_INFO_H

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define MAX_BUFF_SIZE  1024
#define FILE_DESC_SIZE 2
#define IS_CHILD       0
#define MESSAGE_SIZE   12
#define READ_INDEX     0
#define WRITE_INDEX    1
#define FILE_DIR_1     "/workspace/CSCI_474/Project1/filedat1"
#define FD_ERROR       -1

// Function prototype
void readFile(char *buffer);

#endif

Process_info.c

#include "process_info.h"

void readFile(char *buffer) {
    int fileDesc;
    const char *filename = FILE_DIR_1;

    fileDesc = open(filename, O_RDONLY);
    if (fileDesc == FD_ERROR) {
        perror("Cannot open input file");
        exit(1);
    }

    ssize_t n = read(fileDesc, buffer, MAX_BUFF_SIZE);
    if (n < 0) {
        perror("read failed");
        close(fileDesc);
        exit(1);
    }

    if (n > 0) {
        write(STDOUT_FILENO, buffer, n);
    }

    close(fileDesc);
}

main.c

#include "process_info.h"

int main(void) {
    const char *msg = "hello world\n";
    char msg_buffer[MAX_BUFF_SIZE] = {0};
    char file_buffer[MAX_BUFF_SIZE] = {0};
    int fileDes[FILE_DESC_SIZE];

    readFile(file_buffer);

    if (pipe(fileDes) == -1) {
        perror("pipe");
        return 1;
    }

    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        return 1;
    }

    if (pid == IS_CHILD) {
        close(fileDes[READ_INDEX]);
        write(fileDes[WRITE_INDEX], msg, MESSAGE_SIZE);
        close(fileDes[WRITE_INDEX]);
        _exit(0);
    }

    close(fileDes[WRITE_INDEX]);
    ssize_t n = read(fileDes[READ_INDEX], msg_buffer, MESSAGE_SIZE);
    if (n > 0) {
        write(STDOUT_FILENO, msg_buffer, n);
    }
    close(fileDes[READ_INDEX]);

    return 0;
}

Compile with:

gcc -Wall -Wextra -O2 main.c process_info.c -o newmain

And finally, it worked!

Expanding the Project

Once I had the basic version compiling, I wanted to practice more. I added extra functionality:

  • Command-line argument to pick which file to read.
  • Helper functions: readFileIntoBuffer and writeAll for safer I/O.
  • Better parent/child roles: child sends file contents, parent prints them.

This way, I could practice pipes, process management, and file handling more realistically. I even tried running:

./pipe_demo /etc/hosts

And watched the parent print the file contents sent through the pipe by the child.

Final Thought

The biggest lesson I learned is this: an undefined reference error usually means a mismatch between declaration and definition. It’s not that the compiler hates me it just can’t find the function body. A single letter out of place (readFIle vs readFile) wasted me hours of head scratching. But fixing that small mistake turned into a valuable learning moment. I not only solved the error but also built a stronger understanding of how compilation, linking, and process communication work in Linux. Now, instead of fearing these errors, I see them as stepping stones for growth.

Related blog posts