取消引用未知的内存位置:C程序员大多使用scanf()函数来获取输入,但是有时一个小错误可能会带来错误,甚至会使整个程序崩溃。
scanf()的语法为scanf( “%d”,&a);。 。现在可能会错过&并以现成的scanf(“%d”,a)写为&a 。正在取消引用到未知位置。
现在,该程序可能会异常终止,或者可能对应于有效位置(与当前程序无关,但与某些其他程序无关),并且可能被覆盖,这可能在以后导致未知效果。
// A C program to demonstrate that missing
// an & in scanf() may cause memory problems.
#include
int main()
{
int a;
scanf("%d", a);
printf("%d", a);
return 0;
}
读取未初始化的内存。在C语言中,初学者通常使用malloc()提供运行时内存,但是使用malloc()时,不会初始化内存块,因此可以访问。
// A C program to demonstrate that missing
// an & may cause memory problems.
#include
int main()
{
int* p = (int*)malloc(sizeof(int) * 4);
int i;
// p[] contains some garbage value so
// below loop does not make any sense.
for (i = 0; i < 4; i++)
p[i] += 100;
}
一种解决方案是使用calloc()代替,它将块初始化为0。
缓冲区溢出:这是在C中发生的一个非常常见的错误,由于在C本身中存在错误的函数(即用于将字符串作为输入的gets()函数)而变得更加常见。它不检查提供的用于在程序中存储字符串的内存,因此,如果用户输入更大的字符串,则get()在字符串之后覆盖内存位置并导致get()并导致溢出。
void read()
{
char str[20];
gets(str);
printf("%s", str);
return;
}
由于get()不执行任何数组绑定测试,因此代码遭受缓冲区溢出的困扰。得到()不断阅读,直到它看到一个字符。为避免缓冲区溢出,应使用fgets()代替gets(),因为fgets()确保读取的字符不能超过MAX_LIMIT个。
#define MAX_LIMIT 20
void read()
{
char str[MAX_LIMIT];
fgets(str, MAX_LIMIT, stdin);
printf("%s", str);
return;
}
内存泄漏当未使用的堆内存未分配时,由于主内存最终被填满,而可用内存变少,就会出现这种情况。
/* Function with memory leak */
#include
void f()
{
int* ptr = (int*)malloc(sizeof(int));
/* Do some work */
return; /* Return without freeing ptr*/
}
如果不再使用内存,则应该在malloc()之后使用free()。
/* Function without memory leak */
#include
void f()
{
int* ptr = (int*)malloc(sizeof(int));
/* Do some work */
free(ptr); // Deallocate memory
return;
}
由于优先级而导致的错误对运算符及其优先级的了解较少,可能会产生错误,尤其是对于诸如此类的指针
// C program to demonstrate bug introduced due
// to precedence.
#include
int demo()
{
int a = 10;
int* p = &a;
// intention was to increase the value of a
*p++;
}
*的优先级(解除引用/间接运算符不相乘)和后缀++并不相同,但是前缀++和*具有相同的优先级,因此,首先p的值将增加并指向错误的内存区域,然后解除引用并将覆盖该位置或程序可能会终止。有关详细信息,请参见++ * p,* p ++和* ++ p之间的区别。
发送不存在的变量的地址局部变量的返回地址会导致问题,
#include
int fun()
{
int x = 10;
return &x;
}
int main()
{
int* p = fun();
*p = 20;
}
当函数fun()称为变量a时,将创建一个,但是一旦函数返回,它就会被销毁。由于返回了函数,其地址p将指向堆栈区域中的存储区,如果调用了另一个函数,则指针p的更改可能会导致错误。
指针算术指针算术可能会令人困惑,让我们举一个例子,假设整数为4字节。
int main()
{
int x[10] = { 0 }, i = 0, *p;
// p point to starting address of array x
p = &x[0];
while (i < 10) {
*p = 10;
// intention was to point to integer at x[1]
p = p + 4;
i++;
}
}
尽管由于整数为4个字节并且p在起始位置看起来是正确的,所以将其加4将导致p指向数组n中的下一个整数,但是指针算术根据其数据类型的大小工作,因此将1加到指针n整数,然后将sizeof(int)添加到其中,这同样适用于指向任何其他数据类型的指针。
int main()
{
int x[10] = { 0 }, i = 0, *p;
p = &x[0]; // p point to starting address of array x
while (i < 10) {
*p = 10;
p++; // means p = p + sizeof(int)
i++;
}
}
将数组作为参数传递当我们将数组传递给函数,始终将其视为函数的指针。这就是为什么我们永远不要在数组参数上使用sizeof的原因。我们应该总是将size作为第二个参数。
#include
// arr is a pointer even if we have
// use square brackets.
void printArray(int arr[])
{
int i;
/* sizeof should not be used here to get number
of elements in array*/
int arr_size = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < arr_size; i++) {
printf("%d ", arr[i]);
}
}
int main()
{
int arr[4] = { 1, 2, 3, 4 };
printArray(arr);
return 0;
}
下面是更正的代码
#include
// arr is a pointer even if we have
// use square brackets.
void printArray(int arr[], int arr_size)
{
int i;
for (i = 0; i < arr_size; i++) {
printf("%d ", arr[i]);
}
}
int main()
{
int arr[] = { 1, 2, 3, 4 };
int arr_size = sizeof(arr) / sizeof(arr[0]);
printArray(arr, arr_size);
return 0;
}
参考 :
计算机系统:程序员的观点