MST

星途 面试题库

面试题:C语言联合体在复杂内存映射系统中的优化

在一个具有多块连续内存映射区域的系统中,每个区域有不同的用途,例如有的区域用于存储设备状态(可按字节、32位整数访问),有的区域用于数据传输(可按16位整数、64位浮点数访问)。请设计一个基于C语言联合体的内存映射模型,要求尽量减少内存浪费,并且提供高效的访问接口。同时阐述在多线程环境下,对这个联合体进行读写操作可能面临的问题及解决方案。
44.9万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

1. 基于C语言联合体的内存映射模型设计

#include <stdio.h>
#include <stdint.h>

// 定义联合体来表示不同用途的内存映射区域
union MemoryMap {
    // 用于存储设备状态
    struct {
        uint8_t statusByte;
        uint32_t statusWord;
    } deviceStatus;
    // 用于数据传输
    struct {
        uint16_t dataHalfWord;
        double dataDouble;
    } dataTransfer;
};

// 初始化内存映射区域
void initMemoryMap(union MemoryMap *mmap, uint8_t statusByte, uint32_t statusWord, uint16_t dataHalfWord, double dataDouble) {
    mmap->deviceStatus.statusByte = statusByte;
    mmap->deviceStatus.statusWord = statusWord;
    mmap->dataTransfer.dataHalfWord = dataHalfWord;
    mmap->dataTransfer.dataDouble = dataDouble;
}

// 获取设备状态字节
uint8_t getDeviceStatusByte(const union MemoryMap *mmap) {
    return mmap->deviceStatus.statusByte;
}

// 获取设备状态32位整数
uint32_t getDeviceStatusWord(const union MemoryMap *mmap) {
    return mmap->deviceStatus.statusWord;
}

// 获取数据传输16位整数
uint16_t getDataHalfWord(const union MemoryMap *mmap) {
    return mmap->dataTransfer.dataHalfWord;
}

// 获取数据传输64位浮点数
double getDataDouble(const union MemoryMap *mmap) {
    return mmap->dataTransfer.dataDouble;
}

2. 多线程环境下读写操作面临的问题

  • 数据竞争:多个线程同时读写联合体的不同成员,可能导致数据不一致。例如,一个线程正在更新deviceStatus,而另一个线程同时读取dataTransfer,可能会读到部分更新的数据。
  • 缓存一致性:现代CPU通常有高速缓存,不同线程可能在各自的缓存中保留联合体的副本。如果一个线程更新了联合体,其他线程可能不会立即看到更新后的值。

3. 解决方案

  • 互斥锁(Mutex):使用互斥锁来保护对联合体的读写操作。在读写联合体之前,线程必须先获取互斥锁,操作完成后释放互斥锁。
#include <pthread.h>

pthread_mutex_t memoryMapMutex = PTHREAD_MUTEX_INITIALIZER;

// 线程安全的获取设备状态字节
uint8_t getDeviceStatusByteThreadSafe(const union MemoryMap *mmap) {
    pthread_mutex_lock(&memoryMapMutex);
    uint8_t result = mmap->deviceStatus.statusByte;
    pthread_mutex_unlock(&memoryMapMutex);
    return result;
}
  • 读写锁(Read - Write Lock):如果读操作频繁,写操作较少,可以使用读写锁。多个线程可以同时进行读操作,但写操作时需要独占锁。
#include <pthread.h>

pthread_rwlock_t memoryMapRwlock = PTHREAD_RWLOCK_INITIALIZER;

// 线程安全的获取设备状态字节(读操作)
uint8_t getDeviceStatusByteThreadSafeRead(const union MemoryMap *mmap) {
    pthread_rwlock_rdlock(&memoryMapRwlock);
    uint8_t result = mmap->deviceStatus.statusByte;
    pthread_rwlock_unlock(&memoryMapRwlock);
    return result;
}

// 线程安全的设置设备状态字节(写操作)
void setDeviceStatusByteThreadSafe(union MemoryMap *mmap, uint8_t statusByte) {
    pthread_rwlock_wrlock(&memoryMapRwlock);
    mmap->deviceStatus.statusByte = statusByte;
    pthread_rwlock_unlock(&memoryMapRwlock);
}
  • 原子操作:对于简单的成员(如整数类型),可以使用原子操作。C11标准引入了<stdatomic.h>头文件,提供了原子类型和操作函数,可避免数据竞争问题。例如:
#include <stdatomic.h>

atomic_uint8_t atomicDeviceStatusByte;

// 线程安全的获取原子设备状态字节
uint8_t getAtomicDeviceStatusByte() {
    return atomic_load(&atomicDeviceStatusByte);
}

// 线程安全的设置原子设备状态字节
void setAtomicDeviceStatusByte(uint8_t statusByte) {
    atomic_store(&atomicDeviceStatusByte, statusByte);
}