📜  C程序中与通用内存/指针相关的错误

📅  最后修改于: 2021-05-30 12:27:58             🧑  作者: Mango

取消引用未知的内存位置: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;
}

参考 :
计算机系统:程序员的观点

想要从精选的最佳视频中学习和练习问题,请查看《基础知识到高级C的C基础课程》。