Click the blue "Most Programmer" to follow me!
Add a "star" to receive daily technical learning at 18:03.
C is a very popular programming language due to its simplicity and wide application across various fields. However, due to the nature of C, it is also prone to certain errors and pitfalls that can lead to program crashes, data loss, or security vulnerabilities. This article will introduce 15 common pitfalls in C programming and provide corresponding solutions.
At the end, there is an interview question, and everyone is welcome to discuss and learn.
1. Operator Precedence
C has many operators, such as addition, subtraction, multiplication, division, and logical operators. In expressions, different operators have different precedence levels, and failing to notice this can lead to errors. For example:
int a = 5, b = 3;
int c = a++ * --b; // a=6, b=2, and c=10.
In this example, ++ and — have higher precedence than *, so a and –b are executed first, followed by the multiplication operation. If the above code is written like this, the result will be completely different:
int a = 5, b = 3;
int c = ++a * b--; // Now a=6, b=2, c=18
Here, ++a and b– are executed first, followed by the multiplication operation.
Solution: Understand the precedence of each operator correctly and use parentheses to clarify the order of operations in expressions.
2. Case Sensitivity
In C, variable names and function names are case-sensitive. This means that myVar and MyVar are two different variable names. This can easily lead to confusion and errors, for example:
int MyVar = 5;
int myvar = 3;
printf("%d\n", MyVar + myvar); // Outputs 8
Solution: Maintain consistency by using a unified naming convention to avoid confusion.
3. Array Out of Bounds
Array out of bounds refers to accessing an array beyond its defined limits. This can lead to program crashes or data corruption. For example:
int arr[3] = {1, 2, 3};
int x = arr[3]; // Out of bounds access
Solution: Be mindful of the array’s boundary limits and avoid accessing elements outside of its range.
4. Integer Overflow
In C, integer overflow is a common issue. When an integer exceeds its representable range, its value wraps around, i.e., it goes from the maximum value to the minimum value, or vice versa. For example:
unsigned char x = 255;
x += 1; // Now x's value is 0
Solution: Use appropriate data types to avoid exceeding their representable ranges.
5. Pointer Issues
Pointers are an important concept in C, but they can also lead to errors. For example, if a pointer is assigned NULL and is then used without checking, it can produce strange results:
int *p = NULL;
*p = 5; // Error: Accessing a null pointer
Solution: Check if a pointer is NULL before using it.
6. Random Number Seed
In C, when using the rand() function to generate random numbers, you need to first set a seed using the srand() function. If no seed is set, the same sequence of random numbers will be generated each time the program runs. For example:
for (int i = 0; i < 10; i++) {
printf("%d ", rand()); // Outputs the same sequence of numbers
}
If the srand() function is not used to set a seed, the same sequence of random numbers will be generated each time the program runs because the rand() function generates an initial seed based on the current time and uses it to generate pseudo-random numbers. Therefore, it is generally recommended to set a new seed each time the program runs, such as using the time() function to get the current time as the seed value to ensure that the generated random number sequence is sufficiently random.
Solution: Use the time() function in the program to obtain a random seed.
srand(time(NULL));
7. String Handling
In C, a string is an array of characters terminated by a null character ‘\0’. However, if you accidentally forget to add the null character or access the string out of bounds, it can lead to issues. For example:
char str[10] = "hello";
str[5] = 'w'; // Error: No null character added
printf("%s\n", str); // Outputs "hellow"
In C, strings are character arrays that end with a null character (‘\0’). When declaring a character array, the array length must be one more than the actual number of characters stored to accommodate the final null character. In this example, the declared character array str has a length of 10, storing 5 characters “hello” and 1 null character (‘\0’). When we assign the 6th character to ‘w’, although the character ‘w’ exists in the array, there is no corresponding null character following it, making the character array not a valid string.
Since the printf() function uses the null character (‘\0’) to determine the end of the string, when the string does not contain a null character (‘\0’), printf() will continue to output any content immediately following its memory location until it finds a null character (if it cannot find one, it will lead to undefined behavior). In this example, the memory area immediately following the character array str may contain other data, so printf() may output something we do not want to see. To fix this issue, you need to manually add a null character (‘\0’) at the end after modifying the string, making the array a valid C-style string:
char str[10] = "hello";
str[5] = 'w';
str[6] = '\0';
printf("%s\n", str);
Thus, the output will be “hello” followed by a space and “w”.
8. Loop Conditions
When writing loops, if the condition is incorrect, it may lead to infinite loops or the loop body not executing at all. For example:
int i = 0;
while (i < 10) {
printf("%d ", i);
}
In this loop, the condition i<10 is always true, so the loop will continue indefinitely.
Solution: Carefully check the loop condition to ensure it can correctly terminate the loop.
9. Variable Scope
Variables in C have different scopes, and failing to understand this concept can lead to errors. For example:
int x = 1;
if (x == 1) {
int y = 2;
}
printf("%d\n", y); // Error: y's scope is within the if statement block
Solution: Understand variable scope and ensure variables are defined and used in the correct locations.
10. Type Conversion
In C, type conversion is a common operation but can also lead to errors. For example:
int a = 5;
double b = 2.0;
printf("%f\n", a / b); // Outputs incorrect result
In this code, a is an integer variable, and b is a double variable. When performing division, the compiler performs implicit type conversion, converting the integer variable a to a double before performing the division, resulting in a double result. Since the printf() function uses the %f format specifier to output floating-point numbers (including float and double types), even if the result is an integer, it will be interpreted as a floating-point number and output in decimal form.
However, in this case, the output may differ from the expected result. According to C’s integer division rules, the result of dividing two integers is also an integer, and the decimal part will be truncated. Therefore, in this example, the result of 5/2 should be 2 instead of 2.5. Thus, the correct output format should use %f to output a floating-point number:
int a = 5;
double b = 2.0;
printf("%f\n", (double)a / b);
Here, the integer variable a is explicitly converted to double, ensuring that the division of an integer by a floating-point number does not result in implicit type conversion, yielding a double result that can be correctly output by the %f format specifier.
11. Function Calls
In C, function calls are an important operation but can also lead to issues. For example, when calling a function, the types and number of parameters must match those in the function declaration, or it will result in a compilation error. For example:
int add(int a, int b) {
return a + b;
}
printf("%d\n", add(1, 2, 3)); // Error: Incorrect number of parameters
Solution: Ensure that the types and number of parameters in the function call match those in the function declaration.
12. Struct Access
In C, a struct is a custom data type consisting of multiple member variables. To access struct members, the ‘.’ operator is used. However, if the struct pointer is NULL or the struct member does not exist, it can lead to errors. For example:
struct Person {
char name[10];
int age;
};
struct Person *p = NULL;
printf("%s\n", p->name); // Error: Accessing null pointer
Solution: Check if struct pointers and members are NULL or exist before using them.
13. File Operations
In C, file operations are important. However, if files are not opened or closed correctly, it can lead to issues. For example:
FILE *fp = fopen("test.txt", "r");
// Operate on the file...
fclose(fp);
In the above code, if the fopen() function fails, it will return a NULL pointer, and using fclose() will result in an error.
Solution: Ensure that files are opened and closed correctly and check their return values when using file operation functions.
14. Macro Definitions
In C, macro definitions are preprocessor directives that can be used to define constants, functions, etc. However, if macros are not used correctly, it can lead to program errors. For example:
#define SQUARE(x) x * x
int a = 2;
int b = SQUARE(a + 1); // Error: Gets incorrect result
In this example, SQUARE(a+1) expands to a+1*a+1, resulting in an incorrect result.
Solution: Use parentheses to clarify the order of operations in macro definitions and avoid using expressions with side effects in macros.
15. Multithreading
In C, multithreading programming is a complex technique. If thread synchronization mechanisms are not used correctly, it can lead to errors such as data races and deadlocks. For example:
void *print_message(void *ptr) {
char *message = (char *) ptr;
printf("%s\n", message);
pthread_exit(NULL);
}
pthread_t t1, t2;
char *msg1 = "Thread 1";
char *msg2 = "Thread 2";
pthread_create(&t1, NULL, print_message, (void *) msg1);
pthread_create(&t2, NULL, print_message, (void *) msg2);
In this example, both threads will access the printf() function simultaneously, which may lead to jumbled output.
Solution: Use synchronization mechanisms to ensure correct cooperation between threads.
Interview Question
Below is an interview question about C language pitfalls. Please try to answer:
int a = 0, b = 1, c = 2, d = 3;
if (a++ && b-- || c++ && d--) {
printf("case - %d %d %d %d\n", a, b, c, d);
} else {
printf("case + %d %d %d %d\n", a, b, c, d);
}
What does the above code output? Why?
Looking forward to everyone’s answers and discussions!
Source: Embedded Lecture Hall