C/C++ 面试复习笔记(1)

发布于:2025-06-03 ⋅ 阅读:(22) ⋅ 点赞:(0)

什么是C语言中的位域(bit-field)?它有哪些应用场景?

答案:位域允许程序员在结构体中按位定义变量,用于节省内存空间。

解析:位域通常用于需要节省内存的场景,如硬件编程、协议解析等。位域的大小通常不能超过指定的位数,且通常用于表示标志位。

补充:

1.

#include <stdio.h>
​
struct pack
{
 unsigned a:2;  // 取值范围为:0~3
 unsigned b:4;   // 取值范围为:0~15
 unsigned c:6;   // 取值范围为:0~63
};
​
int main(void)
{
 struct pack pk1;
 struct pack pk2;
 
 // 给pk1各成员赋值并打印输出
 pk1.a = 1;
 pk1.b = 10;
 pk1.c = 50;
 printf("%d, %d, %d\n", pk1.a, pk1.b, pk1.c);
 
 // 给pk2各成员赋值并打印输出
 pk2.a = 5;
 pk2.b = 20;
 pk2.c = 66;
 printf("%d, %d, %d\n", pk2.a, pk2.b, pk2.c);
​
 return 0;
}
pk1.a = 1, pk1.a = 10, pk1.c = 5
pk2.a = 1, pk2.b = 4, pk2.c = 2

显然,结构体变量pk1的各成员都没有超出限定的位数,能够正常输出。而结构体变量pk2的各成员超出了限定的位数,并发生了上溢(溢出中的一种)。

2.位域的存储同样遵循结构体内存对齐的规则

3.无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。

C语言中如何实现内存池(memory pool)管理?

答案:内存池是通过预先分配一定大小的内存块来优化内存分配和释放的操作,减少频繁的malloc和free 调用。

解析:内存池通常用于需要频繁申请和释放内存的小对象,避免内存碎片化。实现内存池时,通常预分配一个大块内存,并将其划分为多个小块。

补充:

1.内存池的实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
​
/*1.定义内存池结构体
首先,我们需要定义一个结构体来表示内存池。该结构体通常会包含内存池的大小、内存池的起始位置、以及一个链表或栈,用于管理空闲内存块。
*/
#define POOL_SIZE 1024
#define BLOCK_SIZE 128
​
typedef struct MemoryBlock {
    struct MemoryBlock *next;
} MemoryBlock;
​
typedef struct MemoryPool {
    void *pool_start;
    MemoryBlock *free_blocks;
    size_t pool_size;
} MemoryPool;
​
/*2. 初始化内存池
在初始化内存池时,我们将内存池的内存区域分配给pool_start,并将所有内存块按链表连接起来,供后续使用。
*/
MemoryPool* create_memory_pool(size_t pool_size, size_t block_size) {
    MemoryPool *pool = (MemoryPool *)malloc(sizeof(MemoryPool));
    if (!pool) {
        return NULL;
    }
​
    pool->pool_size = pool_size;
    pool->pool_start = malloc(pool_size);
    if (!pool->pool_start) {
        free(pool);
        return NULL;
    }
​
    pool->free_blocks = (MemoryBlock *)pool->pool_start;
    MemoryBlock *current_block = pool->free_blocks;
​
    for (size_t i = 1; i < pool_size / block_size; i++) {
        current_block->next = (MemoryBlock *)((char *)current_block + block_size);
        current_block = current_block->next;
    }
    current_block->next = NULL;
​
    return pool;
}
​
/*3. 分配内存块
分配内存时,我们从内存池的空闲链表中获取一个空闲的内存块。每次分配时,将链表中的第一个内存块返回,并更新链表头指针。
*/
void* pool_alloc(MemoryPool *pool) {
    if (!pool->free_blocks) {
        return NULL;
    }
​
    MemoryBlock *block = pool->free_blocks;
    pool->free_blocks = block->next;
​
    return (void *)block;
}
​
/*4. 释放内存块
释放内存时,我们将内存块返回给内存池,并将其加入到空闲链表中。
*/
void pool_free(MemoryPool *pool, void *ptr) {
    MemoryBlock *block = (MemoryBlock *)ptr;
    block->next = pool->free_blocks;
    pool->free_blocks = block;
}
​
/*5. 销毁内存池
当内存池不再使用时,需要释放内存池本身的内存。
*/
void destroy_memory_pool(MemoryPool *pool) {
    free(pool->pool_start);
    free(pool);
}
​
int main() {
    MemoryPool *pool = create_memory_pool(POOL_SIZE, BLOCK_SIZE);
    
    if (!pool) {
        printf("内存池创建失败\n");
        return -1;
    }
​
    void *block1 = pool_alloc(pool);
    void *block2 = pool_alloc(pool);
    
    printf("分配了内存块:%p, %p\n", block1, block2);
    
    pool_free(pool, block1);
    pool_free(pool, block2);
​
    destroy_memory_pool(pool);
    return 0;
}

2.内存池的优缺点 优点 提高性能:内存池通过减少内存分配和释放的次数,提高了内存管理的效率,特别是在高频率分配内存的场景中。 减少内存碎片:内存池通过统一管理内存块,避免了动态分配带来的内存碎片问题。 可控性强:内存池的管理方式可以根据应用场景进行定制,提供了更好的控制。 缺点 内存池大小固定:一旦内存池大小确定,无法动态调整。如果内存池太小,可能会导致内存不足;如果内存池太大,可能会浪费内存。 管理复杂:内存池的实现需要程序员手动管理内存,增加了代码的复杂性和出错的风险。 内存泄漏风险:如果内存池中分配的内存块没有正确释放,可能会导致内存泄漏。

C语言中的setjmp和longjmp函数有什么作用?

答案:setjmp 和longjmp 用于非本地跳转,setjmp 保存程序的状态,longjmp恢复状态, 用于实现异常处理机制

解析:setjmp和longjmp通常用于错误处理和跳出嵌套的函数调用。它们提供了一种跳出当前执行流并返回到指定点的机制。

补充:

setjmp() 是 C 标准库 <setjmp.h> 中的一个宏,用于保存当前的程序执行状态,以便在稍后的某个时候通过 longjmp() 来返回到该状态。

C 库宏 int setjmp(jmp_buf environment) :创建本地的 jmp_buf 缓冲区并且初始化,用于将来跳转回此处。这个子程序保存程序的调用环境于 env 参数所指的缓冲区,env 将被 longjmp 使用。如果是从 setjmp 直接调用返回,setjmp 返回值为 0。如果是从 longjmp 恢复的程序调用环境返回,setjmp返回非零值。

声明

下面是 setjmp() 宏的声明。

#include <setjmp.h>
​
int setjmp(jmp_buf env);

参数

  • env:一个 jmp_buf 类型的变量,用于保存当前的程序执行状态。

返回值

  • 如果 setjmp() 直接调用,返回值为 0。

  • 如果 setjmp() 是通过 longjmp() 调用返回的,返回值是由 longjmp() 设置的非 0 值。

实例

下面的实例演示了 setjmp() 宏的用法。

实例

#include <stdio.h>
#include <setjmp.h>
 
static jmp_buf buf;
 
void second(void) {
    printf("second\n");         // 打印
    longjmp(buf,1);             // 跳回setjmp的调用处 - 使得setjmp返回值为1
}
 
void first(void) {
    second();
    printf("first\n");          // 不可能执行到此行
}
 
int main() {   
    if ( ! setjmp(buf) ) {
        first();                // 进入此行前,setjmp返回0
    } else {                    // 当longjmp跳转回,setjmp返回1,因此进入此行
        printf("main\n");       // 打印
    }
 
    return 0;
}

让我们编译并运行上面的程序,这将产生以下结果:

second
main

使用场景

  • 错误处理和异常处理setjmp()longjmp() 可以用于实现简单的错误和异常处理机制。

  • 状态机:在状态机的状态转换中,有时需要在不同的状态之间跳转,setjmp()longjmp() 可以用于实现这种状态转换。

  • 与信号处理一起使用:在一些特殊情况下,setjmp()longjmp() 可以用于从信号处理程序中跳出。

注意事项

  • 使用 setjmp()longjmp() 需要非常小心,因为它们会导致程序的控制流程变得非常混乱,从而导致代码难以理解和调试。

  • 避免在涉及多线程或信号处理程序的环境中使用 setjmp()longjmp(),因为它们可能会导致未定义的行为。

总结

setjmp()longjmp() 提供了一种非常灵活的机制,可以在程序的不同部分之间进行跳转。尽管它们对于某些特定情况下的控制流程改变非常有用,但在实际使用时需要小心谨慎,以避免潜在的问题和未定义的行为。


网站公告

今日签到

点亮在社区的每一天
去签到