基础BUG
算术BUG
整数
加法
加法上溢出
int a = INT32_MAX - 1;
int b = a + 10;
加法下溢出
int a = INT32_MIN + 1;
int b = a - 10;
减法
减法上溢出
int a = 10;
int b = a - INT32_MIN;
减法下溢出
int a = -10;
int b = a - INT32_MAX;
取反
有符号整数取反上溢出
int a = INT32_MIN;
int b = -a;
对于有符号整数,取反没有下溢出,因为有符号整数的最小值的绝对值大于最大值的绝对值
无符号整数取反下溢出
对于无符号整数,除了0,其他任何数取反都会下溢出
unsigned int a = 10;
unsigned int b = -a;
乘法
乘法上溢出
int a = INT32_MAX / 2;
int b = INT32_MAX / 2;
int c = a * b;
整数乘法下溢出
int a = INT32_MIN / 2;
int b = INT32_MIN / 2;
int c = a * b;
除法和模运算
除零通常会造成硬件异常
除零
int a = 10;
int b = 0;
int c = a / b;
移位
有符号整数左移溢出
因为有符号位,有符号整数允许左移的最大位数要比自身的位数小1。
int a = 0;
int b = a << 31;
无符号整数左移溢出
无符号整数允许左移的最大位数通常和自身位数相等。
unsigned int a = 0;
unsigned int b = a << 32;
浮点数
inf和nan
在计算机中,inf和NaN是浮点数中的特殊值。
inf表示无穷大,它在计算机中通常用一个特殊的二进制表示,即所有位都是1,且指数部分为全1,尾数部分为0。
NaN表示非数字(Not a Number),它在计算机中通常用一个特殊的二进制表示,即指数部分为全1,尾数部分不为0。
加法
上溢出
float a = FLT_MAX - 1;
float b = a + 2.0;
assert(b == INF);
下溢出
float a = FLT_MIN + 1;
float b = a - 2.0;
assert(b == -INF);
减法
float a = 2.0;
float b = a - FLT_MIN;
assert(b == INF);
下溢出
float a = -2.0;
float b = a - FLT_MAX;
assert(b == -INF);
乘法
float a = FLT_MAX;
float b = a * 2.0;
assert(b == INF);
下溢出
float a = FLT_MIN;
float b = a * 2.0;
assert(b == -INF);
除法
事实上除零并不一定完全就是除以0,因为浮点数的精度有误差,事实上除以一个很小的数,小于FLT_EPS等同于除零。
正数除零上溢出
float a = 2.0;
float b = a / 0.0;
assert(b == INF);
负数除零上溢出
float a = -2.0;
float b = a / 0.0;
assert(b == -INF);
零除零上溢出
float a = 0.0;
float b = a / 0.0;
assert(b == NAN);
内存BUG
内存越界(overflow)
堆内存越界
int main() {
int* ptr = (int*)malloc(8 * sizeof(int));
ptr[10] = 10;
free(ptr);
return 0;
}
栈内存越界
int main() {
int arr[10] = {0};
arr[10] = 10;
return 0;
}
全局内存越界
int arr[10] = {0};
int main() {
arr[10] = 10;
return 0;
}
内存泄漏(leak)
直到程序退出也没有释放的内存往往会导致内存泄漏。
int* func() {
int* ptr = (int*)malloc(1024 * sizeof(int));
return ptr;
}
int main() {
int* ptr = func();
return 0;
}
双重释放(doble free)
int* func() {
int* ptr = (int*)malloc(1024 * sizeof(int));
free(ptr);
return ptr;
}
int main() {
int* ptr = func();
free(ptr);
return 0;
}
释放后使用(use after free)
int* func() {
int* ptr = (int*)malloc(1024 * sizeof(int));
free(ptr);
return ptr;
}
int main() {
int* ptr = func();
*ptr = 10;
return 0;
}
野指针
以下类型统称为野指针
空指针
初始化为空的指针
int main() {
int* p = NULL;
*p = 10;
return 0;
}
#### 随机指针
没有被初始化就被使用的指针往往指向随机值,当然也可能是空值。
```c
int main() {
int* p;
*p = 10;
return 0;
}
悬垂指针
悬垂通常指该指针指向的内容的生命周期已经结束,最典型的是返回栈上变量的地址。
int* func() {
int a = 0;
return &a;
}
int main() {
// 这就是悬垂指针
int* ptr = func();
return 0;
}
### 栈溢出
以下程序在某些机器下可能会栈溢出,注意func的形参和实参都被分配在栈上,可能会导致栈内存不足。
```c
long func(int arr[]) {
long sum = 0;
...
return sum;
}
int main() {
int arr[1024 * 1024];
long sum = func(arr);
printf("%ld", sum);
return 0;
}
并发BUG
为了可移植性,这里使用tinycthread的api。
数据读写竞争
锁相关BUG
#include <tinycthread.h>
int value = 0;
typedef struct thr_call_s
{
thrd_t thr;
int push;
int ret;
} thr_call;
int thr_routine(void* data)
{
// 不同线程改写同一全局变量,会导致未定义行为,触发BUG
value = (thr_call*)data->push;
return 0;
}
int main(int argc, char const *argv[])
{
thr_call val[5] = {0};
for (size_t i = 0; i < sizeof(val) / sizeof(*val); i++)
{
val[i].push = i;
thrd_create(&(val[i].thr), thr_rountine, &(val[i]));
}
for (size_t i = 0; i < sizeof(val) / sizeof(*val); i++)
{
thrd_join(val[i].thr, &(val[i].ret));
}
// 查看返回值
for (size_t i = 0; i < sizeof(val) / sizeof(*val); i++)
{
printf("return:[%d], expect:[%d]\n", val[i].ret, i%2==0?0:1);
}
return 0;
}
死锁
#include <tinycthread.h>
mtx_t mtx1;
mtx_t mtx2;
typedef struct thr_call_s
{
thrd_t thr;
int push;
int ret;
} thr_call;
void print(void* data) {
for (size_t i = 0; i < 5; i++)
{
printf("thread:[%d], index:[%d]\n", ((th_call*)data)->push, i);
}
}
int thr1_routine(void* data)
{
mtx_lock(&mtx1);
mtx_lock(&mtx2);
print(data);
mtx_unlock(&mtx2);
mtx_unlock(&mtx1);
return ((thr_call*)data)->push % 2 == 0? 0 : 1;
}
int thr2_routine(void* data)
{
mtx_lock(&mtx2);
mtx_lock(&mtx1);
print(data);
mtx_unlock(&mtx1);
mtx_unlock(&mtx2);
return ((thr_call*)data)->push % 2 == 0? 0 : 1;
}
int main(int argc, char const *argv[])
{
thr_call val[2] = {0};
mtx_init(&mtx1, mtx_plain);
mtx_init(&mtx2, mtx_plain);
val[0].push = 0;
val[1].push = 1;
thrd_create(&(val[0].thr), thrd1_rountine, &(val[0]));
thrd_create(&(val[1].thr), thrd2_rountine, &(val[1]));
thrd_join(val[0].thr, &(val[0].ret));
thrd_join(val[1].thr, &(val[1].ret));
mtx_destroy(&mtx1);
mtx_destroy(&mtx2);
return 0;
}
不可重入锁重入
#include <tinycthread.h>
mtx_t mtx;
typedef struct thr_call_s
{
thrd_t thr;
int push;
int ret;
} thr_call;
void print(void* data) {
// 这里会触发BUG,一直等待
mtx_lock(&mtx);
for (size_t i = 0; i < 5; i++)
{
printf("thread:[%d], index:[%d]\n", ((th_call*)data)->push, i);
}
mtx_unlock(&mtx);
}
int thr_routine(void* data)
{
mtx_lock(&mtx);
print(data);
mtx_unlock(&mtx);
return ((thr_call*)data)->push % 2 == 0? 0 : 1;
}
int main(int argc, char const *argv[])
{
thr_call val[5] = {0};
mtx_init(&mtx, mtx_plain);
for (size_t i = 0; i < sizeof(val) / sizeof(*val); i++)
{
val[i].push = i;
thrd_create(&(val[i].thr), thr_rountine, &(val[i]));
}
for (size_t i = 0; i < sizeof(val) / sizeof(*val); i++)
{
thrd_join(val[i].thr, &(val[i].ret));
}
// 查看返回值
for (size_t i = 0; i < sizeof(val) / sizeof(*val); i++)
{
printf("return:[%d], expect:[%d]\n", val[i].ret, i%2==0?0:1);
}
mtx_destroy(&mtx);
return 0;
}
锁在同一线程多次释放
#include <tinycthread.h>
mtx_t mtx;
typedef struct thr_call_s
{
thrd_t thr;
int push;
int ret;
} thr_call;
void print(void* data) {
for (size_t i = 0; i < 5; i++)
{
printf("thread:[%d], index:[%d]\n", ((th_call*)data)->push, i);
}
mtx_unlock(&mtx);
}
int thr_routine(void* data)
{
mtx_lock(&mtx);
print(data);
// 这里会触发BUG,被第二次释放
mtx_unlock(&mtx);
return ((thr_call*)data)->push % 2 == 0? 0 : 1;
}
int main(int argc, char const *argv[])
{
thr_call val[5] = {0};
mtx_init(&mtx, mtx_plain);
for (size_t i = 0; i < sizeof(val) / sizeof(*val); i++)
{
val[i].push = i;
thrd_create(&(val[i].thr), thr_rountine, &(val[i]));
}
for (size_t i = 0; i < sizeof(val) / sizeof(*val); i++)
{
thrd_join(val[i].thr, &(val[i].ret));
}
// 查看返回值
for (size_t i = 0; i < sizeof(val) / sizeof(*val); i++)
{
printf("return:[%d], expect:[%d]\n", val[i].ret, i%2==0?0:1);
}
mtx_destroy(&mtx);
return 0;
}