Home > Software engineering >  How to properly link libraries with gcc
How to properly link libraries with gcc

Time:12-30

I have 2 libraries made from scratch in c, vector.h and string.h with the source file string.c (the vector.h only has define directives, no need for source file), and used the #pragma once directive so that the header files would only be included one time. The string library uses the vector library, and then both are included in main.c.

With that setup, i ran into two problems:

  • if i simply did gcc main.c -o build/main.exe, it couldn't find the function definitions in string.c for the function declarations in string.h (undefined reference to `stringFunction')
  • if i first compiled the source files into objects, and then compiled them into the executable (gcc -c string/string.c -o build/obj/string.o, gcc -c main.c -o build/obj/main.o, gcc -o build/main.exe build/obj/main.o build/obj/string.o, ./build/main.exe), it said there were multiple definitions of the same functions (build/obj/string.o: in function `vectorCreate_String': string.c:(.text 0x0): multiple definition of `vectorCreate_String'; build/obj/main.o:main.c:(.text 0x0): first defined here)

Folder structure:

PROJECT
    - string
        - string.c
        - string.h
    - vector
        - vector.h
    - main.c

vector.h

#pragma once

#include <stdlib.h>

#define DEFINE_VECTOR(type) typedef struct { \
    unsigned int size; \
    size_t bytesize; \
    type *data; \
} Vector_##type; \
Vector_##type *vectorCreate_##type(unsigned int initialSize, type *initialElements) { \
    Vector_##type *v = (Vector_##type*)malloc(sizeof(Vector_##type)); \
    v->size = initialSize; \
    v->bytesize = initialSize * sizeof(type); \
    v->data = (type*)malloc(v->bytesize); \
    for (unsigned int i=0; i<initialSize; i  ) v->data[i] = initialElements[i]; \
    return v; \
} \
void vectorPush_##type(Vector_##type *v, type element) { \
    v->size  ; \
    v->bytesize  = sizeof(type); \
    v->data = (type*)realloc(v->data, v->bytesize); \
    v->data[v->size-1] = element; \
} \
void vectorFree_##type(Vector_##type *v) { \
    free(v->data); \
    free(v); \
} \
Vector_##type *vectorSlice_##type(Vector_##type *v, unsigned int start, unsigned int end) { \
    Vector_##type *slice = (Vector_##type*)malloc(sizeof(Vector_##type)); \
    slice->size = end - start; \
    slice->bytesize = slice->size * sizeof(type); \
    slice->data = (type*)malloc(slice->bytesize); \
    for (unsigned int i=0; i<slice->size; i  ) slice->data[i] = v->data[start   i]; \
    return slice; \
}

#define Vector(type) Vector_##type
#define vectorCreate(type, initialSize, initialElements) vectorCreate_##type(initialSize, initialElements)
#define vectorPush(type, vector, element) vectorPush_##type(vector, element)
#define vectorFree(type, vector) vectorFree_##type(vector)
#define vectorSlice(type, vector, start, end) vectorSlice_##type(vector, start, end)

string.h

#pragma once

#include "../vector/vector.h"

typedef struct {
    unsigned int length;
    char *str;
} String;


DEFINE_VECTOR(String);


String *stringCreate(char *str);

String *stringAppend(String *string, char *str);

void stringFree(String *string);

String *stringSlice(String *string, unsigned int start, unsigned int end);

Vector(String) *stringSplit(String *string, char *delimiter);

string.c

#include "string.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

String *stringCreate(char *str) {
    String *string = malloc(sizeof(String));
    string->length = strlen(str);
    string->str = malloc(string->length   1);
    strcpy(string->str, str);
    return string;
}

String *stringAppend(String *string, char *str) {
    string->length  = strlen(str);
    string->str = realloc(string->str, string->length   1);
    strcat(string->str, str);
    return string;
}

void stringFree(String *string) {
    free(string->str);
    free(string);
}

String *stringSlice(String *string, unsigned int start, unsigned int end) {
    String *newString;
    newString->length = end - start;
    newString->str = malloc(newString->length   1);
    for (unsigned int i=0; i<newString->length; i  ) newString->str[i] = string->str[start   i];
    newString->str[newString->length] = '\0';
    return newString;
}

// not implemented yet
Vector(String) *stringSplit(String *string, char *delimiter) {
    return NULL;
}

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "./string/string.h"

// stuff to test if everything is working properly
int main(int argc, char *argv[]) {
    Vector(String) *strings = vectorCreate(String, 0, NULL);
    vectorPush(String, strings, *stringCreate("Hello"));
    vectorPush(String, strings, *stringCreate("World"));
    printf("%s %s\n", strings->data[0].str, strings->data[1].str);
    vectorFree(String, strings);
    return 0;
}

CodePudding user response:

A "function declaration" is usually put into a .h file.

A "function definition" is usually put into a .c file.

DEFINE_VECTOR provides function definitions and you use it in string.h. Which means the function vectorCreate_String() for example is defined both in string.c and main.c due to both including string.h.

I suggest you add another macro in vector.h that only generates function declarations and move that typedef struct from DEFINE_VECTOR to that macro. Use that macro in string.h, and then use DEFINE_VECTOR in string.c to provide definitions for the declared functions in string.h.

  • Related