makefile - RicoJia/notes GitHub Wiki

Basics

  • Motivations:
  • Linked List, stacks, are customized and stored in each companie's code base.
  • A library contain: 1. reusable functions, 2. expandable macros 3. constants. E.g, Djikstra needs heap library and graph.
  • Cool features:
    1. make file is smart: it compares the timestamp of a file and its object files. Then it works on the changes made to files. If h file is changed, all src files linking to it will be recompiled. If a src file is changed, only its object file will be recompiled.

The "Compilation" Process

Summary

  • General "Compilation" consists of 4 stages: preprocessing -> compilation -> assembling -> linking
  • As a summary,
    • Preprocessing takes in your code, then does text substitution only on header files
    • Compilation takes in the pre-processed code, and transforms it to assembly code
    • Assembler takes in the assembly code, and produces machine code (compiled code) in object files, which can be used in libraries
    • Linking takes in the assembled code and produces a binary executable. You need one and only one main function!

Pre-processing Directives (everything before compilation)

  • # means preprocessing directives:

    • #include
      • Everything in .h will be copied to the source file
    • #define
      • Used for macros, like #define square(x)(x*x)
  • Pre-processing is just text substitution, happens in source file (not in header file)

    • copy header files to src -> copy macros -> expand all macros -> final compilation unit (ready for compilation)
    • You can manually perform manual pre-processing and get the final compilation unit by gcc -E SRC -o OUT.i
  • The purpose of pre-processing directives is to help us avoid duplicate header inclusions

    • Duplicate header inclusion: header_A includes header_B, in src you include header_A and header_B at the same time.
  • Types of pre-processing directives:

    • #define: substitues a pre-processor macro
    • #undef : undefine something
    • #ifdef, #endif : return true if macro is defined, and what's in between will be executed
    • #ifndef: return true if macro is not defined
    • #if : test a compile time condition
    • #else
    • #elif
    • #endif: end pre-processor conditional
    • E.g random example
      #define A 10
      #ifdef A      //will return true, and the line below will be executed
      #include "lol.h"
      #endif
      #undef A      // remove definition of A
      #ifdef A      // What's next will not be executed
      #include "lol.h"
      #endif            
    • E.g include guard - Duplicate header inclusion, always include this!!
        //A.h
        #ifndef __A__
        #define  __A__
        ... //your headerfile
        #endif
        //B.h
        #ifndef __B__
        #define __B__
        #include "A.h"
        ...
        #endif
        // src.c
        #include "A.h"
        #include "B.h"  // preprocessing will expand this, and because of the include guards, no duplication
      • A neat way to comment stuff out:*because /*, / won't work with comments inside
        • the code wouldn't even get compiled
          #if 0
            ....
          #endif    
  • Recursive Dependency

    • Compiler needs to know the size of a struct before compiling it.
    • But now you have two structures dependent on each other
    • Solution is "forward declaration": tells the compiler that something will be defined in the future
      struct occ_t;     // This is forward declaration
      struct cmp{
            occ_t* occ; 
      };
      struct occ_t{
            cmp hee; 
      };

Compilation

  • Manually create an assembly code
    gcc -S src.c -o src.s 

Assembling

  • Manually create object files
gcc -c src.s src.o
  • To see the assembly code inside an object file
objdump -D src.o 
  • The object files, and the executables are in the ELF(Executable and Linking) format

Linking

  • A program will first be loaded into virtual address space, then it needs to find system libraries.
  • Linking provides
    • access to global variables, functions defined in other files
    • Make library functions available, such as printf, scanf.
      • static linking: copy library code Overview
      • dynamic linking: dynamic linker of Operating system loads shared libs to executables during runtime, from disk to RAM, and filling jump tables and relocating pointers. So that the lib code is available during runtime
        • A dynamically linked process has a small statically linked function.
          • The function is called when the program starts
          • The function only maps the address of the program into memory
          • Link library will determine which dynamic libs need to be called. Then, it will resolve refs to these dynamic libs (shared libs). This is why we need position Independent Code

Regular compilation

  • Only .cpp or .c are compilation units.
  • Compilation is to create object files. Creation of executable is called linking
  • Basic Compilation workflow
      gcc -c lib.c -o lib.o   #-c means compile. -o means output. object files contain machine instructions, not human readable. 
      gcc -g -c app.c -o app.o  #-g means gdb flags
      gcc -g app.o lib.o -o exe   #linking all object files and creating an executable 
  • C++ workflow
     # Compilation
     	g++ -c john.cpp -o john.o
     	g++ -c sam.cpp -o sam.o
     #Linking
     	ar ru libfamily.a john.o sam.o	
     	ranlib libfamily.a
    • ar is archiver, we need archive for ranlib.
    • randlib will build the symbol index table for each object file.
      • This is not necceary many mahchines.
    • Notes
      • -pthread should be added to compiler if you see anything related to it
      • -lboost_system should be added too.

Static Library vs dynamic Library

  • A library is just one compilation unit, with a bunch of object files

  • A library must be application agnostic - library doesn't know what applications will do.

  • Differences

    • static Linking happens in compilation, dynamic linking happens during runtime
    • a copy static libraries is owned by a process, dyanmic is shared by processes
  • Static Library

    • definition: A collection of portable linked object file with functions.
    • Pros:
      • since lib has been linked internally during compilation, no additional run-time costs.
      • No runtime querying for symbols, so faster.
    • Cons:
      • larger binary files
      • Need to be recompiled everytime the lib is updated
    • basic workflow: lib<>.a
      gcc -c src1.c -o src1.o
      gcc -c src2.c -o src2.o
      ar rs libsrc.a src1.o src2.o  #ar means "archive" You need to bundle the object files together
      #Linking 
      gcc -c application.c -o application.o
      gcc application.o -o output -L . -lsrc  #1. This line is to link application.o to the library. 
      #-L means linking, . means it loads the current directory, or you can put or you can do /home/vm/... and -l is short for "lib ... .a" 

#- Dynamic Lib: - Pros: - Small executable - No need for recompilation, as long as binary compatible?? - Cons: - Slower in run time - Potential compatiblity issue if lib is changed. - Basic workflow - dynamic: .so (shared object) ```shell script gcc -c -fPIC src1.c -o src1.o #PIC means Position-Independent-Code gcc -c -fPIC src2.c -o src2.o #PIC means Position-Independent-Code gcc src1.o src2.o -shared -o libsrc.so

    # Linking to Dynamic Library
    gcc -c application.c -o application.o
    sudo cp libsrc.so /usr/lib/
    sudo ldconfig
    gcc application.o -o out -ldll   
		# this doesn't have -L, (linking) 
		# because the dynamic lib is already in the shared location, so gcc will automatically search there.
		#you may also cp to /usr/local/lib
		#Else, specify the path: export LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH 
    ```

Components

Header files:

  • Header files are non-compilation units, do not compile header files!

  • Header files Contain anything that need to be exposed to source files. Each source file has a definition of header. Also, it tells developers what is there, not how its done :

    • structure
    • function declaration
    • Constants, Enumerations
    • Macros
    • Static inline functions
  • header file works to solve:

    • Define structure definitions before use
    • declare function prototype before use.
  • header files need to follow some rules:

    • "Define-and-use" rule
      • When you define a structure that's dependent on another structure, you must define the other structure first. This is because the compiler works from top to bottom
    • "Declare-and-use" rule
      • You must declare functions first, before using it! ("prototype" or "signature")
      • Compiler doesn't need source files.
    • But a header file can only have one definition, (source file), otherwise. This is why you need linking to reuse source files.

Makefile

  • Motivation: Tool runs on Linux and Unix, to help build software programs that are dependent on other programs.
    • you may have so many object files, how do you build static/dynamic libraries??
    • You can use make files to do
      • FULL Compilation process
      • Install Libraries,
      • Update Dependencies
  • Structure: this is a tree.
    • You need "rules" to define what to do
      •    <FINAL_PRODUCT>:<RAW_MATERIALS>
              <ACTION>    
        • <FINAL_PRODUCT> is just a variable, not a keyword
        • Use tab, not spaces, for
      • E.g,
      TARGET:EXECUTABLE_NAME
      common_math.o:common_math/common_math.c
          gcc -c -I common_math common_math/common_math.c -o common_math/common_math.o
      rico_app: main.o libcalc.a
          gcc main.o -o rico_app -L . libcalc.a -lm
      clean: 
          rm common_math/common_math.o
      • You need to specify executable
    • you can have only one Makefile in the root directory by running make
    • sequence inside makefile: Highest level is always on top?? No, this is a tree, that does DFS based on the variable names.
    • You need to provide a clean rule. To run that, make clean
  • Tricks
    • make common_math.o You can make one intermediate file
⚠️ **GitHub.com Fallback** ⚠️