Bo2SS

Bo2SS

7 Engineering Project Development

Course Content#

Function Declaration and Definition#

  • Declaration: Tells the system that this thing exists

    • The variable name of the incoming parameter is not important, it does not need to be specified at this time
  • Definition: How it is specifically implemented

  • Previously, function declarations and definitions were done simultaneously

  • Compilation order: from top to bottom, from left to right

    • Image
    • Above: gcc error message; below: g++ error message (maybe g++ error is friendlier)

    • When looking at errors, look from top to bottom, subsequent errors may be a chain reaction caused by the first error

  • Function undeclared and undefined exposed in two periods

    • Function undeclared error - compilation process (mainly syntax checking)
      • g++ -c *.cpp generates the compiled object file
      • Image
    • Function undefined error - linking process
      • g++ *.o links to generate the executable program

      • Image
    • The above error messages come from the captain's clang compiler, we are using the g++ compiler, so the display is different
  • Function declarations can be made multiple times, but definitions can only be made once!

Header Files and Source Files#

  • Specifications
    • Header files contain declarations, source files contain definitions
      • Should not all be placed in the header file
    • Header files and corresponding source files have the same name
  • Conditional compilation in header files can avoid the problem of duplicate inclusion of header files during one compilation process
#ifndef _HEADER1_  // The name is best corresponding to the header file name, though there is no hard requirement
#define _HEADER1_
...
#else              // Can be omitted
#endif             // Must be present

Engineering Development Specifications and Static Libraries#

  • Can the double quotes "" after #include be changed to angle brackets <>?
    • Double quotes "": search from the directory where the executing code is located
    • Angle brackets <>: search from the system library path
    • Use g++/gcc -I to add header file paths to the system library path
  • When developing upwards
    • Provide others with header files (include folder) and the corresponding object file package (lib folder)
    • Object file packaging
      • Static library (.a)

// Packaging
ar -r libxxx.a header1.o header2.o header3.o
// Linking g++ *.o -L -l
g++ test.o -L./lib -lxxx
// xxx corresponds

Makefile Tool#

  • Document compilation tool, similar to markdown

  • Encapsulates the compilation process, reducing the complexity of compilation during program development

  • Example

    • Image
    • .PHONY opens a virtual environment to avoid conflicts with existing clean files in the path when using make clean

    • Can have encapsulated variable replacement operations

Introduction to Google Test Framework#

  • Unit testing

    • Also known as module testing, is the testing work for correctness verification of program modules (the smallest unit of software design)
      • In procedural programming, a unit is a single program, function, procedure, etc.
    • The framework follows the language: C++, Python, Java...
  • Implemented in C++

  • CMake tool

    • Can generate makefile files based on the environment of the local machine
    • Why not use makefile directly? Makefile has strong requirements on the environment
    • The Google Test framework can be compiled by first running cmake and then make, paying attention to the location of the packaged library
  • Code (main.cpp)

    • Image
    • Uses the gtest.h header file enclosed in angle brackets <>

    • add2 is just an identifier

    • What is an assertion?

      • Used to catch the programmer's own errors: assuming a certain situation occurs, but if it does not occur, take appropriate action
      • ASSERT_* version of assertions will produce a fatal failure and terminate the current function when they fail
      • EXPECT_* version of assertions produce a non-fatal failure and will not terminate the current function
  • Makefile

    • Image
    • Can use -std=xxx to specify the C++ version standard, but it is not necessary on the local machine

    • Need to use -I to add the header file path ./lib

    • Use -lpthread to additionally link the pthread library, which will be automatically linked on mac systems

    • 🆗 Questions

      • Image
      • ① The default standard is set based on the compiler version, c++11 is a relatively low version
      • ② It is possible that operations like make install were used, which included the header files into the system library directory
  • Result

  • Image

⭐ Implement Your Own Testing Framework#

  • Implemented in C
  • Need to implement the following three functions or macros
TESTEXPECT_EQRUN_ALL_TEST
FunctionalityRepresents a test caseTest points within the test caseRun all TEST
Macro/FunctionMacroFunction or macroFunction or macro
NotesNo return value type;
Forms a valid function definition with the following curly braces {}
A type of assertionReturn value is 0

Version One: Compile and Display Test Results#

  • haizei/test.h

    • Image
    • Cannot use a##.##b

      • Function names can only contain underscores, letters, and numbers, cannot have "."
    • a##haizei##b

      • Using haizei or similar special identifiers is to prevent a and b from directly connecting and causing function name conflicts
      • For example (test, funcadd) and (testfunc, add)
    • attribute((constructor))

  • haizei/test.cc

    • Image
    • Just need to define symbolically, can compile

  • main.cpp

    • Image
    • Three groups of TEST

  • Makefile

    • Image
    • Using make can quickly compile

    • Pay attention to the use of -o, allowing object files and executable programs to be custom-named and placed in a specified directory

    • Pay attention to specifying the folder where the file is located in the path

  • Test Results

    • Image
  • ❓ In the current version, whether the return from main() is RUN_ALL_TESTS() or 0, it will display the test results. How can RUN_ALL_TESTS() control whether to display the output?

Version Two: RUN_ALL_TESTS() Switch#

  • The original intention of implementing the framework - switch control

  • Points to be recorded

    • How many groups of test cases
    • Function names corresponding to the test cases
    • Functions corresponding to the test cases
      • Use function pointer variables
      • Use an array to record function pointers
  • haizei/test.h

    • Image
    • In TEST, use add_function to record functions into global variables before the main function executes

    • The second use of typedef: elevating a variable to a type

    • Use of structures: encapsulating function pointers and function names

  • haizei/test.cc

Use malloc() to allocate space to copy the string, returning its address, i.e., the string pointer;
Remember to use free() to release it

    • main.cpp and makefile remain unchanged
    • ❗ Switch control has been implemented, and the display, assertions, etc. can be optimized below!

Version Three: Humanized Optimization#

① Add Color to Output#

  • Reference Colored printf - Blog

  • Define color encapsulation as macros in the header file haizei/test.h

    • Image
    • COLOR normal

    • COLOR_HL highlight

    • COLOR_UL underline

    • Multiple strings can be connected with spaces

    • Note! There should be no spaces around ";" in color control characters

Correct: "\033[1;""31" "m" "%s\n"
Invalid setting, nothing: "\033[1; ""31" "m" "%s\n"

② Add Assertion Macros#

  • Check not equal, greater than, greater than or equal to, less than, less than or equal to

  • Specific to the point: implement each macro separately

  • Unified management type: similar to defining color macros, encapsulate the common code again

    • Image
    • Master the use of #

③ Count the Number of Successful and Failed Test Points for Each Group of Tests and Display#

  • haizei/test.h

    • Image
    • Image
    • Define a structure for statistics, unified management, better encapsulation

    • Perform statistics at the assertion point

    • Use extern to declare structure variables here, because

      • The assertion point in the header file uses this variable, so it needs to be declared

int i is both a declaration and a definition, extern int i is just a declaration
struct FunctionInfo haizei_test_info is both a declaration and a definition
Just declare it with extern in front

  • haizei/test.cc
    • Image
    • Define and declare haizei_test_info variable
    • 1.0 elevates the type, placing 100.0 in front may overflow
    • 100% situation judgment: use a very small value and fabs for floating-point equality; successful count == total count
    • Center alignment effect
      • %m.nf: occupies m columns in total, with n decimal places, if the numerical width is less than m, left fill spaces
      • %-m.nf: occupies n columns in total, with n decimal places, if the numerical width is less than m, right fill spaces

④ ⭐ Display Detailed Information of Failed Test Points#

  • Mainly write the LOG macro to be executed in the assertion macro in the header file

  • haizei/test.h

    • Image
    • ⭐ The result value type of the actual part is uncertain, define a generic macro
      • _Generic(a, replacement rules): implement corresponding replacements based on the return type of a
      • _Generic is a keyword in C language, not a macro! It will not be replaced with the corresponding type during the preprocessing stage
        • ① Be very careful when using it with COLOR macros!
          • During the compilation stage, a string cannot be concatenated with something unknown (_Generic())
        • ② Cannot use C++ compiler
        • ❗ See the following Error One and Error Two
      • Reference cpp_reference
    • Use typeof to define additional variables
      • All operations are done through additional variables to avoid multiple calculations caused by + operations
    • Error One (Compilation Stage -c)
      • Image
      • Corresponding erroneous writing: putting TYPE(a) inside the YELLOW_HL macro
        • Image
        • The red box ② can output normally, but will have no color
        • If wrapped in a color macro like red box ①, it will report an error during compilation
          • Preprocessing main.c will not report an error
          • Check the preprocessed code of red box ② above, as follows
          • Image
          • Reason: For the code after macro replacement, ("string" _Generic() "string") will report an error during compilation, as the compiler does not know what _Generic() is at that time
          • _Generic() needs to know the result at runtime, so during syntax checking, concatenating a string with something unknown will cause an error
          • This has little to do with the first input parameter type of printf() being const char*, but type mismatch will raise a warning
          • Looking at this simple example may clarify:
            • Header file
            • Image
            • Source file
            • Image
            • Compile
            • Image
            • The same error
            • Because during the syntax check phase, the compiler does not know what s is, and concatenating it with the string "a" will cause an error
            • The error message suggests you to remove s, directly adding parentheses in front of s
      • Therefore, wrapping _Generic() with sprintf() is very clever, as it has no issues during the compilation stage, and will work normally at runtime when it has values.
    • Error Two (Compilation Stage -c)
      • Image
      • Image
      • Key information in the second image's error

error: '_Generic' was not declared in this scope

* 
    * _Generic only supports C language (C11), not C++
        * Reference [How to Enable _Generic Keyword](https://www.thinbug.com/q/28253867) - ThinBug
    * Change all file extensions to C language
        * main.cpp → main.c; test.cc→test.c
    * Modify makefile, see below
  • main.c

    • Image
    • Test double type data to verify the effect of the generic macro
      • Changed the function parameter type to double
      • In fact, double equality cannot be directly used with ==, in the header file, use the difference and a very small value for equality check
  • Makefile

    • Image
    • Change to gcc

  • Output

    • Image

⑤ Store Global Variables for Functions Without Limiting the Number of Test Cases#

  • Static arrays: allocate a fixed size of space before running, and the stored physical space is continuous
  • Linked lists: conceptually sequential, but do not need to be sequential in physical storage
    • Composed of nodes, containing: data field, pointer field
    • Occupies space that changes dynamically
    • But the more powerful way is the following: you can give any structure a linked list skeleton
  • ⭐⭐Linked List Skeleton
    • haizei/test.h
      • Image
      • Directly add a node structure variable to a structure, i.e., the skeleton of the linked list
      • node records the address of the next node (the next TEST's node)
      • Header file containing linked list nodes haizei/linklist.h
    • haizei/linklist.h
      • image-20210329094033910
      • next points to the address of the next node
        • But actually wants to access the func and str fields of the next TEST
        • Can complete this by accessing the address of the next structure and then indirectly accessing the two fields
      • How to get the address of a structure
        • Calculate the offset of the field name in structure T through pointer p
        • offset macro!
          • Use a null pointer to get the address of the name field
            • (T *)(NULL)->name gets the name variable
            • & gets a pointer of type T *, which stores the address
          • Convert to long type to get the offset
            • long type will change its range according to system bitness, corresponding to pointer size
        • Head macro!
          • Convert the address of pointer p to char * type
          • This way ±1 offsets by the smallest unit of 1 byte
          • p is a pointer, name is the field name corresponding to pointer p in structure T
    • haizei/test.c
      • Image
      • Tail insertion method, define a tail node pointer func_tail
      • Get the address of the structure, use -> to indirectly access variables
      • The main difference between malloc() and calloc()
        • The former does not initialize the allocated memory space, while the latter initializes the allocated space to 0 by default

// Dynamically allocate a block of memory of specified size size in the heap to store data
void* malloc (size_t size);
// Dynamically allocate num blocks of continuous space of length size in the heap, initializing each byte to 0
void* calloc (size_t num, size_t size);

      • Also applicable to strdup, copying a string to newly allocated space and returning its address
      • The space of calloc and strdup needs to be freed manually
        • Image
        • The above image is for reference
        • ① Before freeing the func space of calloc, save the address of the next node
          • Use p->next
        • ⭐② Free structure variables from inside to outside
          • func->str allocated by strdup
          • func allocated by calloc
        • ③ After freeing, set the pointer to NULL to avoid becoming a dangling pointer
        • When freeing the space pointed to by func->str, it needs to be cast to (void *)
        • Pay attention to details when freeing structures, see later considerations: details of freeing structures
        • Check the addresses of variables in func
          • Image
          • Image
          • Aligned to 8 bytes
          • Printing func->str prints the address allocated by strdup, printing &(func->str) prints the address of the member str in the structure object

⑥ Macro Optimization for Function Pointer Variables and Function Names#

  • Method One: Macro Replacement Optimization NAME, STR2
    • Image
  • Method Two: Macro Nesting NAME, STR, _STR

Additional Knowledge Points#

  • Place function declarations and the main function at the top, and place function definitions later, to make the code framework and logic clearer

  • Simple engineering file structure specifications

    • Use the tree tool
  • Image
  • Rules of make

    • If there are modified dependency files in the makefile
      • Directly make, related files will be automatically recompiled, and there is no need to make clean for cleanup
    • If only the makefile has been modified and you want to regenerate object files
      • Generally, you need to make clean first, then use make to regenerate new object files, otherwise, it will only regenerate the output of the top-level all
  • Executable programs are generally placed in a fixed directory: bin

  • Macro internal comments

    • Single-line macros: can directly use // comments afterwards
    • Multi-line macros: can only use /.../ comments
  • Header files only write function declarations

  • Macro nesting macros

  • attribute((constructor)), see Implement Your Own Testing Framework - Version One

  • Handling Long Lines in C Language - CSDN

  • ❗ Details of # in macro definitions

    • Stringization operator
    • Function: Converts the parameter name in the macro definition into a string enclosed in a pair of double quotes
    • Can only be used in macro definitions with parameters, and must be placed before the parameter name in the macro definition body
  • typeof(), __typeof(), typeof() Differences - CSDN

    • It is recommended to use the one with an underscore

Points to Consider#

  • Can macro functions be redefined?
    • Function definitions placed in header files may lead to function redefinition issues when compiled multiple times in different files
    • But throwing a macro defined as a function into a header file is fine?
      • It's fine

      • Redefining macro functions is not an issue, as follows

        • Image
        • For this situation, avoid function name conflicts (a##_haizei##b)

    • However! Macros cannot be redefined, that is, previous definitions cannot be modified
  • Macro definitions do not need to consider the order of definitions! && macro nesting issues
#define _ToStr(x) #x 
#define __ToStr(x) _ToStr(x) 
#define EarthQuake 9.0 
  
int main(){ 
    printf("%s\n", _ToStr(EarthQuake);   // EarthQuake
    printf("%s\n", __ToStr(EarthQuake);  // 9.0
    return 0;
} 
    • Replacement order
      • From outer layer to inner layer, but stops expanding when encountering # or ##
      • First: →#EarthQuake→"EarthQuake"
      • Second
        • First layer replacement: →_ToStr(EarthQuake)→_ToStr(9.0)
        • Second layer replacement: _ToStr(9.0)→"9.0"
    • Nested definitions: #define __ToStr(x) _ToStr(x)
    • Nested calls: __ToStr(EarthQuake)
  • ❗ Details of freeing structures
    • free(p) does not change the value of the p variable itself; after calling free(), it will still point to the same memory space, but this memory is now invalid and cannot be used
    • All dynamically allocated spaces need to be released separately, freeing from the structure inside to outside
      • Structures are in heap space, and there are also variables in the structure in heap space, so member variables need to be freed first, and then the structure itself

Tips#

  • aka means "also known as" in Chinese
  • Don't just watch the captain show, think about how to optimize? How to develop? How to turn it into your own knowledge points?
  • If you can't hold on, you must persist; often this is the most valuable
  • When compilation errors occur, look at the error messages from top to bottom, as subsequent errors may also stem from the previous ones

Course Notes#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.