Search code examples
cpathruntime

How To Change Path of a File At Runtime In C?


I Have To Change The Path Given by An Macro of The Custom Header Files At Runtime. Suppose That I Have a Directory Where All My Custom Data Structures are Stored Path: E:\Programming Files\Code\C\Custom Headers The Custom Headers Folder Has Some SubFolders, My Directory WorkFlow Is Like That:

Custom Headers
      |
      |> Array Structure
      |       |
      |       |> Integer_Array.c
      |       |
      |       |> Float_Array.c
      |       |
      |       |> Char_Array.c
      |       |
      |       |> Arrays.h
      |
      |> Stack Structure
      |       |
      |       |> Integer_Stack.c
      |       |
      |       |> Float_Stack.c
      |       |
      |       |> Char_Stack.c
      |       |
      |       |> Stack.h
      |
      |> Queue Structure
      |       |
      |       |> Integer_Queue.c
      |       |
      |       |> Float_Queue.c
      |       |
      |       |> Char_Queue.c
      |       |
      |       |> Queue.h
      |
      |> Structures.h
      |

In Arrays.h, Stack.h & Queue.h The Paths of Structures Are Defined. For Example, In Arrays.h File:

#define Int_Array "E:\Programming Files\Code\C\Custom Headers\Array Structure\Integer_Array.c"
#define Float_Array "E:\Programming Files\Code\C\Custom Headers\Array Structure\Float_Array.c"
#define Char_Array "E:\Programming Files\Code\C\Custom Headers\Array Structure\Char_Array.c"

Just Like That Stack.h and Queue.h Files Are Also Written. And The Final One Is Structures.h:

#define Array "E:\Programming Files\Code\C\Custom Headers\Array Structure\Arrays.h"
#define Stack "E:\Programming Files\Code\C\Custom Headers\Stack Structure\Stack.h"
#define Queue "E:\Programming Files\Code\C\Custom Headers\Queue Structure\Queue.h"

#include Array
#include Stack
#include Queue

With These Files, I made Connection Network Through Out The Directory. Beucause Of It, I Can Able To Include All Structures in The Custom Headers Folder Without Giving Address. For Example: Example.c (File Present In Other Directory) :::

#include <stdio.h>
#include "E:\Programming Files\Code\C\Custom Headers\Structures.h"
#include Int_Array
#include Int_Stack
#include Int_Queue

int main()
{
  printf("Hello World!");
  return 0;
}

This Is Just For Simplicity When Including My Structures at any File in Need.

But The Problem is That I Cannot Modify The Paths At Ones For Any Case. Is There Any Solution To Change All Paths When I include The Structures.h File. Problem.c:

#include <stdio.h>
#define ROOT_PATH "E:\Programming\C" \\Is There Any Thing Like This Can Modify The Value of All Defined Macros.
#include "E:\Programming Files\Code\C\Custom Headers\Structures.h"

int main()
{
  return 0;
}

Solution

  • The issue is that you are fundamentally mis-using macros and #include. While this example compiles, using them appropriately will allow you the flexibility you're looking for.

    Portable, project-independent code like you describe is best implemented as a library. A library is a compiled file, that your program can load at runtime(you can also bundle them in directly, this is called static linking). You can share a library with multiple projects. In fact, you're already doing it! Calls to #include <stdio.c> are including a library.

    You have header files, which define macros that #include c files directly. This is incorrect in several ways.

    A Quick Guide to C Library Structure

    C code is stored in .h and .c files. .h files hold the signatures of all the code, but only .c stores the implementation. .so files are meant to be the bundles.

    From your code, let's assume that there are three libraries that I want to access, an int array, an int stack, and an int queue. Here is custom lib/int_array.c

    #include "int_array.h"
    #include <stdlib.h>
    
    struct int_array {
        int * data,
        int length;
    };
    
    struct int_array * new_int_array(int length) {
        // Code here, etc.
    }
    
    void hidden_func(void) {
        ...
    }
    

    and custom_lib/int_array.h

    #ifndef CUSTOM_LIB_INT_ARRAY_H
    #define CUSTOM_LIB_INT_ARRAY_H
    
    struct int_array;
    
    struct int_array * new_int_array(int length);
    
    #endif
    

    The header file contains protoypes of functions and types. These are not defined, but the interface is defined. This is all the information other code needs to see to be able to call the code and use it correctly.

    In the c file, the code is implemented. Only there do types have meaning. Now that your code is prepared, you can package it into a shareable object, that other programs can receive at runtime and link with.

    First, we compile the code:

    gcc -c -fpic int_array.c
    

    This produces a file called int_array.o. Now we need to package it into something shareable. We do this as so:

    gcc -shared -o libint_array.so int_array.o
    

    This will produce a libint_array.so. We are done! Now our code is ready to be used in other projects.

    In an entirely separate folder, we write proj/main.c:

    #include "int_array.h"
    
    int main() {
       // use int_array
    }
    

    To compile this, we need to tell the compiler where to look for objects. There is a list of places it will automatically look, so we can either

    1.) Copy your library to a place that's already checked. This is called your library path. On linux, it's usually /usr/lib, along with a few others. If you put your .so in the required folder, it will appear.

    2.) Add your library to the search path. You can do this by adding -L /path/to/.so/folder to the compiler.

    I'll assume you chose option 1.

    Now, you compile your project like this:

    gcc main.c -lint_array
    

    This will produce a binary that will link to int_array at runtime.

    Useful properties

    You can instead create a folder in the default search path, to create structure. For example, create the folder /usr/lib/custom, and put your .so files inside this folder. This would let you do #include <custom/int_array.h>, which would clearly show when you're using custom code.

    At runtime, you can change where your compiler looks for libraries, and make it possible to choose which version you link with.

    Also, if you spot a bug in your library, you can fix it, and update the .so. This will cause all projects to immediately use the correct library with the fix. With your method, you then have to recompile every single project that uses the library.

    Important differences to your approach

    The issue with your approach is that you're including .c files instead of .h files. When a .c file is included like this, it effectively copies all of the code in the file directly into your source code. The issue is that this can bring along unwanted functions and global variables, as well as duplicating it's code every time it's called.

    For example, in the example above, int_array.c defines a file called void hidden_func(void);. That function would not be reachable from main.c. This is because only the symbols in the .h file are exported. This allows you to use helper functions that you do not want to share.

    Additionally, if you include a file several times in a program using your method, the code will be copied in each time. That means that your final code will be several times larger than it should be, as each function will be included multiple times.

    When code is included using shared_libraries, it's source is only loaded once.

    Summary

    To change your code to follow this pattern, you can compile the .so s as shown above. To change the version that's included, you can use -L flags to change where your compiler looks for .so. It wi