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
-
-
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
-
- 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 is different
- 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
- Should not all be placed in the header file
- Header files and corresponding source files have the same name
- Header files contain declarations, source files contain definitions
- 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
-
-
- 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, 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...
- Also known as module testing, is the testing work for correctness verification 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 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)
-
-
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
-
-
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
-
- ① 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
-
⭐ Implement Your Own Testing Framework#
- Implemented in C
- Need to implement the following three functions or macros
TEST | EXPECT_EQ | RUN_ALL_TEST | |
---|---|---|---|
Functionality | Represents a test case | Test points within the test case | Run all TEST |
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, 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))
- Set function attributes, used during function declaration or definition
- Causes the first function after 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
- Reference Function Attributes attribute((constructor)) and attribute((destructor)) - CSDN
-
-
haizei/test.cc
-
-
Just need to define symbolically, can compile
-
-
main.cpp
-
-
Three groups of TEST
-
-
Makefile
-
-
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
-
-
❓ 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
-
-
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 of global variables
-
Use of strdup: Reference 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
-
- 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
-
-
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
-
- Master the use of #
-
③ Count the Number of Successful and Failed Test Points for Each Group of Tests and Display#
-
haizei/test.h
-
-
-
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
-
-
- But do not define variables in the header file, as it can easily lead to redefinition issues
- Reference Correct Use of extern Keyword in C Language - CSDN
-
- haizei/test.cc
-
- 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
-
- ⭐ 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
- ① Be very careful when using it with COLOR macros!
- 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)
-
- 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 report an error during compilation
- Preprocessing main.c will not report an error
- Check the preprocessed code of red box ② above, as follows
-
- 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
-
- Source file
-
- Compile
-
- 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)
-
-
- 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
-
- 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
-
-
Change to gcc
-
-
Output
-
⑤ 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
-
- 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
- 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
- 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 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
-
- haizei/test.h
// 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 allocated by malloc in strdup is easily forgotten to be released: Dangerous strdup function
- The space of calloc and strdup needs to be freed manually
-
- 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 *)
- Otherwise, there will be a warning for freeing const char*, reference: In C, why do some people cast the pointer before freeing it? - Stackoverflow
- As mentioned in the link above, freeing const types is quite strange
- Pay attention to details when freeing structures, see later considerations: details of freeing structures
- Check the addresses of variables in func
-
-
- 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
-
-
- 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 reports an error during preprocessing as follows:
-
- Treating a.b as a parameter or variable name is illegal → . has a special meaning
- Reference 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 place function definitions later, to make the code framework and logic clearer
-
Simple engineering file structure specifications
- Use the tree tool
-
-
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
- If there are modified dependency files in the makefile
-
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
- When there are # or ##, macro nesting macros cannot effectively expand, at this time, an additional layer of macro is needed for conversion
- But only the places with # and ## stop expanding, other places will continue to expand
- Reference: Macro Usage Skills in C/C++ (Macro Nesting/Macro Expansion/Variable Argument Macros) - CSDN
- When there are # or ##, macro nesting macros cannot effectively expand, at this time, an additional layer of macro is needed for conversion
-
⭐attribute((constructor)), see Implement Your Own Testing Framework - Version One
-
❗ 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
-
-
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
- When a macro is called, it is directly replaced
- For macro nesting situations, refer to Order of Macro Replacement in C Language - 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 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)
- Replacement order
- ❗ 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