MST

星途 面试题库

面试题:C语言中数组与指针在复杂场景下的内存管理及陷阱规避

在一个C语言项目中,定义了如下结构体:`typedef struct { int *data; int size; } ArrayWrapper;`,要求实现一个函数 `ArrayWrapper* createArray(int n)` 用于创建一个 `ArrayWrapper` 结构体实例,其中 `data` 指向动态分配的包含 `n` 个整数的数组。同时实现一个 `void destroyArray(ArrayWrapper* wrapper)` 函数用于释放相关内存。在实现过程中,会遇到哪些与数组和指针相关的内存管理问题,如何避免?此外,如果在多线程环境下,对这些数组和指针的操作可能会引发哪些常见误区和问题,如何解决?
25.7万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

实现 createArraydestroyArray 函数

#include <stdio.h>
#include <stdlib.h>

typedef struct { 
    int *data; 
    int size; 
} ArrayWrapper;

ArrayWrapper* createArray(int n) {
    ArrayWrapper *wrapper = (ArrayWrapper*)malloc(sizeof(ArrayWrapper));
    if (wrapper == NULL) {
        return NULL;
    }
    wrapper->data = (int*)malloc(n * sizeof(int));
    if (wrapper->data == NULL) {
        free(wrapper);
        return NULL;
    }
    wrapper->size = n;
    return wrapper;
}

void destroyArray(ArrayWrapper* wrapper) {
    if (wrapper != NULL) {
        free(wrapper->data);
        free(wrapper);
    }
}

内存管理问题及避免方法

  1. 内存泄漏
    • 问题:在 createArray 函数中,如果 malloc 分配内存失败,而之前已经分配了部分内存(如 wrapper 分配成功但 wrapper->data 分配失败),若不释放已分配的内存,就会导致内存泄漏。
    • 避免方法:如上述代码,在 wrapper->data 分配失败时,及时释放 wrapper。在 destroyArray 函数中,确保在释放 wrapper->data 后释放 wrapper 结构体本身。
  2. 悬空指针
    • 问题:如果在调用 destroyArray 释放内存后,没有将指针设置为 NULL,而后续代码误操作该指针,就会产生悬空指针,导致未定义行为。
    • 避免方法:在 destroyArray 释放内存后,将指向 ArrayWrapper 的指针设置为 NULL。例如:
ArrayWrapper *wrapper = createArray(10);
destroyArray(wrapper);
wrapper = NULL;
  1. 双重释放
    • 问题:如果多次调用 destroyArray 对同一 ArrayWrapper 指针进行释放,会导致双重释放错误,程序崩溃。
    • 避免方法:在释放指针后将其设置为 NULL,如上述避免悬空指针的方法。另外,可以在代码逻辑上确保不会重复调用释放函数。

多线程环境下的常见误区和问题及解决方法

  1. 数据竞争
    • 问题:多个线程同时访问和修改 ArrayWrapper 中的 data 数组或 size 成员,可能导致数据不一致。例如,一个线程正在读取 data 数组时,另一个线程对其进行修改。
    • 解决方法:使用互斥锁(pthread_mutex_t)来保护对 ArrayWrapper 的访问。例如:
#include <pthread.h>

pthread_mutex_t wrapperMutex = PTHREAD_MUTEX_INITIALIZER;

ArrayWrapper* createArray(int n) {
    pthread_mutex_lock(&wrapperMutex);
    ArrayWrapper *wrapper = (ArrayWrapper*)malloc(sizeof(ArrayWrapper));
    if (wrapper == NULL) {
        pthread_mutex_unlock(&wrapperMutex);
        return NULL;
    }
    wrapper->data = (int*)malloc(n * sizeof(int));
    if (wrapper->data == NULL) {
        free(wrapper);
        pthread_mutex_unlock(&wrapperMutex);
        return NULL;
    }
    wrapper->size = n;
    pthread_mutex_unlock(&wrapperMutex);
    return wrapper;
}

void destroyArray(ArrayWrapper* wrapper) {
    if (wrapper != NULL) {
        pthread_mutex_lock(&wrapperMutex);
        free(wrapper->data);
        free(wrapper);
        pthread_mutex_unlock(&wrapperMutex);
    }
}
  1. 死锁
    • 问题:如果多个线程以不同顺序获取多个互斥锁,可能会导致死锁。例如,线程A获取锁1,然后尝试获取锁2,而线程B获取锁2,然后尝试获取锁1。
    • 解决方法:使用锁顺序规则,确保所有线程以相同顺序获取互斥锁。另外,可以使用死锁检测工具来发现潜在的死锁问题。
  2. 条件变量使用不当
    • 问题:如果在使用条件变量(pthread_cond_t)时没有正确与互斥锁配合,可能导致线程永远等待或误唤醒。
    • 解决方法:在等待条件变量时,必须持有相关的互斥锁。在条件满足时,使用 pthread_cond_signalpthread_cond_broadcast 唤醒等待的线程,并确保在唤醒线程后,线程能够正确获取互斥锁并处理数据。例如:
pthread_cond_t dataReadyCond = PTHREAD_COND_INITIALIZER;

// 假设某个线程填充数组
void* fillArray(void* arg) {
    ArrayWrapper *wrapper = (ArrayWrapper*)arg;
    // 填充数组
    pthread_mutex_lock(&wrapperMutex);
    // 通知其他线程数据已准备好
    pthread_cond_signal(&dataReadyCond);
    pthread_mutex_unlock(&wrapperMutex);
    return NULL;
}

// 假设某个线程读取数组
void* readArray(void* arg) {
    ArrayWrapper *wrapper = (ArrayWrapper*)arg;
    pthread_mutex_lock(&wrapperMutex);
    // 等待数据准备好
    pthread_cond_wait(&dataReadyCond, &wrapperMutex);
    // 读取数组
    pthread_mutex_unlock(&wrapperMutex);
    return NULL;
}