Bo2SS

Bo2SS

7 Engineering Project Development

Course Content#

Function Declaration and Definition#

  • Declaration: Inform the system that this entity exists

    • The variable name of the input 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 (perhaps g++ error messages are friendlier).

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

  • Function undeclared and undefined errors occur 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 may differ.
  • 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.
      • They should not all be placed in the header file.
    • Header files and corresponding source files should have the same name.
  • Conditional compilation in header files can avoid the problem of multiple inclusions 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 the header file path to the system library path.
  • When developing upwards:
    • Provide others with the 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 to

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, it is the testing work to verify the correctness 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 for the environment.
    • The Google Test framework can be compiled by first using cmake and then make, paying attention to the library packaging location.
  • 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 cause a fatal failure and terminate the current function when they fail.
      • EXPECT_* version of assertions will cause a non-fatal failure and will not terminate the current function.
  • Makefile

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

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

    • Use -lpthread to additionally link the pthread library; the mac system will link it automatically.

    • 🆗 Questions

      • Image
      • ① The default standard is set according to the compiler version; c++11 is a relatively low version.
      • ② It may have used operations like make install, which included the header files into the system library directory.
  • Result

  • Image

⭐ Implement Your Own Test Framework#

  • Implemented in C.
  • Need to implement the following three functions or macros
TESTEXPECT_EQRUN_ALL_TEST
FunctionRepresents a test caseTest points in the test caseRun all TESTs
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; "." is not allowed!
    • 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 it symbolically; compilation can proceed.

  • main.cpp

    • Image
    • Three groups of TESTs.

  • Makefile

    • Image
    • Using make allows for quick compilation.

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

    • Note the specification of the folder where the files are located.

  • Test Results

    • Image
  • ❓ In the current version, whether the return from the main() function is RUN_ALL_TESTS() or 0, the test results will be displayed. How can RUN_ALL_TESTS() control whether the output is displayed or not?

Version Two: RUN_ALL_TESTS() Switch#

  • The original intention of implementing the framework - switch control.

  • Points to record:

    • How many groups of test cases.
    • The function names corresponding to the test cases.
    • The 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 usage 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 at the end.

    • main.cpp and makefile remain unchanged.
    • ❗ The switch control has been implemented; optimizations for display, assertions, etc., can now be made!

Version Three: User-Friendly Optimization#

① Add Color to Output#

  • Refer to Colored printf - Blog.

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

    • Image
    • COLOR normal.

    • COLOR_HL highlight.

    • COLOR_UL underline.

    • Connect multiple strings 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 for not equal, greater than, greater than or equal to, less than, less than or equal to.

  • Specific implementation for each macro:

  • Unified management type: Similar to defining color macros, encapsulate common code again.

    • Image
    • Master the use of #.

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

  • haizei/test.h

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

    • Perform statistics at the assertion points.

    • Use extern to declare structure variables here because:

      • The assertion points in the header file use this variable, so a declaration of this variable is needed.

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 at the front.

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

④ ⭐ Display Detailed Information of Failed Test Points#

  • Mainly write the LOG macro to be executed in the assertion macros 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 rule): Implement corresponding replacements based on the return type of a.
      • _Generic is a keyword in C, not a macro! It will not be replaced with the corresponding type during preprocessing.
        • ① Be very careful when using it with COLOR macros!
          • During the compilation phase, a string cannot be concatenated with an unknown (_Generic()).
        • ② Cannot use C++ compiler.
        • ❗ See below for Error One and Error Two.
      • Refer to cpp_reference.
    • Use typeof to define additional variables.
      • All operations are done through additional variables to avoid multiple calculations caused by + and ++ operations.
    • Error One (Compilation Phase -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 cause a compilation error.
          • Preprocessing main.c will not cause an error.
          • Check the preprocessed code of red box ②, as follows:
          • Image
          • Reason: For the code after macro replacement, ("string" _Generic() "string") will cause a compilation error because the compiler does not know what _Generic() is at that time.
          • _Generic() needs to know the result at runtime, and during syntax checking, it cannot concatenate strings with something unknown, hence the error.
          • This is not significantly related to the first input parameter type of printf() being const char*, but type mismatch will raise a warning.
          • A simple example below may clarify:
            • Header file
            • Image
            • Source file
            • Image
            • Compilation
            • Image
            • The same error occurs.
            • Because during the syntax checking phase, the compiler does not know what s is, and concatenating it with the string "a" will cause an error.
            • The error message suggests removing s, adding parentheses before s.
      • Therefore, wrapping _Generic() with sprintf() is a clever way; it has no issues during compilation and works normally at runtime when it has values.
    • Error Two (Compilation Phase -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++.
        * Refer to [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 comparison cannot be done directly with ==; in the header file, use a difference with a very small value for comparison.
  • Makefile

    • Image
    • Use gcc instead.

  • Output

    • Image

⑤ Global Variable Storage for Functions Has No Limit on the Number of Test Cases#

  • Static arrays: Fixed space size is allocated before running, and the stored physical space is contiguous.
  • Linked lists: Conceptually sequential, but do not require physical storage to be sequential.
    • Composed of nodes, containing: data field, pointer field.
    • Occupies space that changes dynamically.
    • However, the following method is even more powerful: it 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.
      • The 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 access the two fields 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 the pointer of type T*, which stores the address.
          • Convert to long type to get the offset.
            • long type changes its range based on 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 the variable.
      • 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 in the heap to store data.
void* malloc (size_t size);
// Dynamically allocate num blocks of continuous space of 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 for calloc and strdup needs to be freed by the user.
        • Image
        • Refer to the above image.
        • ① Before freeing the func space of calloc, save the address of the next node.
          • Use p->next.
        • ⭐② Free structure variables from inside out.
          • func->str is allocated by strdup.
          • func is 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 is necessary to use (void *) to cast.
        • Pay attention to details when freeing structures; see later points on the 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, while 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 function definitions at the bottom to make the code framework and logic clearer.

  • Simple engineering file structure specifications.

    • Use the tree tool.
  • Image
  • Rules of make:

    • If a dependency file in the makefile has been modified:
      • Directly use make, and related files will automatically recompile without needing to use make clean for cleanup.
    • If only the makefile has been modified and you want to regenerate object files:
      • Generally, you need to use make clean first, then use make to regenerate new object files; otherwise, only the top-level all output will be regenerated.
  • Executable programs are generally placed in a fixed directory: bin.

  • Macro internal comments:

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

  • Macro nesting:

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

  • Handling Long Lines in C Language - CSDN.

  • ❗ Details of # in macro definitions:

    • Stringizing 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 input parameters and must be placed before the parameter name in the macro definition body.
  • Differences between typeof(), __typeof(), and typeof() - CSDN.

    • It is recommended to use the one with an underscore.

Points for Consideration#

  • Can macro functions be redefined?
    • Function definitions placed in header files may cause function redefinition issues when compiled multiple times in different files.
    • However, placing a macro defined as a function in the header file is fine?
      • It's fine.

      • Redefining macro functions is not an issue, as shown below.

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

    • However! Macros cannot be redefined, meaning 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 to inner, but stops expanding when encountering # or ##.
      • First type: →#EarthQuake→"EarthQuake".
      • Second type:
        • 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 variable p 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 must be released individually, freeing from the structure inside out.
      • Structures are in heap space, and variables within structures are also in heap space; member variables must be freed first, followed by 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, that's when you need to persist; often, this is the most valuable.
  • When encountering compilation errors, look at the error messages from top to bottom, as subsequent errors may also stem from earlier ones.

Course Notes#

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