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.
-
-
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.
-
- Function undefined error - linking process.
-
g++ *.o links to generate the executable program.
-
-
- The above error messages come from the captain's clang compiler; we are using the g++ compiler, so the display may differ.
- Function undeclared error - compilation process (mainly syntax checking).
-
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.
- Header files contain declarations, source files contain definitions.
- 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
-
-
- Dynamic library (.so)
- Both achieve the same functionality, which is packaging.
- Difference between static and dynamic libraries - Niuke discussion.
-
Makefile Tool#
-
Document compilation tool, similar to markdown.
-
Encapsulates the compilation process, reducing the complexity of compilation during program development.
-
Example
-
-
.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...
- Also known as module testing, it is the testing work to verify the correctness of program modules (the smallest unit of software design).
-
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)
-
-
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
-
-
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
-
- ① 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
-
⭐ Implement Your Own Test Framework#
- Implemented in C.
- Need to implement the following three functions or macros
TEST | EXPECT_EQ | RUN_ALL_TEST | |
---|---|---|---|
Function | Represents a test case | Test points in the test case | Run all TESTs |
Macro/Function | Macro | Function or macro | Function or macro |
Notes | No return value type; Forms a valid function definition with the following curly braces {} | A type of assertion | Return value is 0 |
Version One: Compile and Display Test Results#
-
haizei/test.h
-
-
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))
- Set function attributes to be used during function declaration or definition.
- It allows the first function following it to be automatically called before the main function executes.
- Otherwise, executing RUN_ALL_TESTS() in main.cpp will directly end the program without going through TEST.
- Refer to Function Attributes attribute((constructor)) and attribute((destructor)) - CSDN.
-
-
haizei/test.cc
-
-
Just need to define it symbolically; compilation can proceed.
-
-
main.cpp
-
-
Three groups of TESTs.
-
-
Makefile
-
-
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
-
-
❓ 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
-
-
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 of global variables.
-
Use of strdup: Refer to C Language strdup() Function: Copy String.
-
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.
-
-
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.
-
- Master the use of #.
-
③ Count and Display the Number of Successful and Failed Test Points for Each Group of Tests#
-
haizei/test.h
-
-
-
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.
-
-
- But do not define variables in the header file, as it may lead to redefinition issues.
- Refer to Correct Use of extern Keyword in C - CSDN.
-
- haizei/test.cc
-
- 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
-
- ⭐ 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.
- ① Be very careful when using it with COLOR macros!
- 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)
-
- Corresponding erroneous writing: putting TYPE(a) inside the YELLOW_HL macro.
-
- 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:
-
- 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
-
- Source file
-
- Compilation
-
- 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)
-
-
- 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
-
- 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
-
-
Use gcc instead.
-
-
Output
-
⑤ 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
-
- 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
- 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.
- Use a null pointer to get the address of the name field.
- 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
-
- 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.
-
- haizei/test.h
// 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 allocated by malloc in strdup is easily forgotten to be released: Dangerous strdup Function.
- The space for calloc and strdup needs to be freed by the user.
-
- 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.
- Otherwise, there will be a warning about freeing const char*, refer to: In C, why do some people cast the pointer before freeing it? - Stackoverflow.
- As mentioned in the link above, freeing const types is indeed strange.
- Pay attention to details when freeing structures; see later points on the details of freeing structures.
- Check the addresses of variables in func.
-
-
- 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.
-
-
- Also applicable to strdup, copying a string to newly allocated space and returning its address.
-
⑥ Macro Optimization for Function Pointer Variables and Function Names#
- Method One: Macro Replacement Optimization NAME, STR2.
-
- Method Two: Macro Nesting NAME, STR, _STR.
- STR(NAME(a, b, _)).
- However, cannot use '.' to connect to generate function names; can use '_'.
- a##.##b will cause an error during preprocessing as follows:
-
- Passing a.b as a parameter or variable name is illegal → . has special meaning.
- Refer to error: pasting “.” and “red” does not give a valid preprocessing token - StackOverflow.
-
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.
-
-
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.
- If a dependency file in the makefile has been modified:
-
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:
- When there are # or ##, macro nesting cannot effectively expand; an additional layer of macro is needed for conversion.
- However, only the places with # and ## stop expanding; other places will continue to expand.
- Refer to: Macro Usage Techniques in C/C++ (Macro Nesting/Macro Expansion/Variable Argument Macros) - CSDN.
- When there are # or ##, macro nesting cannot effectively expand; an additional layer of macro is needed for conversion.
-
⭐attribute((constructor)), see Implement Your Own Test Framework - Version One.
-
❗ 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.
-
-
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.
- When a macro is called, it is directly replaced.
- For macro nesting situations, refer to Order of Macro Replacement in C - CSDN.
#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).
- Replacement order:
- ❗ 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.