一个对齐关键字pack引起的副作用

本文通过一个具体案例探讨了在C/C++编程中忽视pack关键字有效范围可能导致的问题。文章详细描述了一个因函数指针赋值异常引发的bug,并分析了其根本原因是不同源文件中数据结构的字节对齐差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天遇到一个很典型的因为没有留意pack关键字有效范围而引起的程序bug,觉得很有意思,就记录下来。

现象如下:声明了一个数据结构 struct st_data,这个数据结构中有一个成员是一个函数指针inic_cb,再定义一个函数st_data_constructor(st_data* my_data),在该函数中对my_data->inic_cb赋值,然后在main函数中定义struct st_data my_data变量,调用st_data_constructor(&my_data),然后再调用my_data.inic_cb();发现程序报了段错误。很奇怪,通过gdb调试时,发现进入st_data_constructor函数后,一切都很好,成员有被赋值,但是出了st_data_constructor函数重新进入main后发现,my_data.inic_cb的值十分怪异,很明显不是一个函数地址。被这个问题阻塞了很久,才突然灵光一现,是不是因为在main函数和st_data_constructor函数中的数据结构字节对齐不同(main函数和st_data_constructor函数处于两个不同的源文件,include了不同的头文件)。发现果然如此。

下面举一个例子说明

pack.h

#ifndef _PACK_H_
#define _PACK_H_

#pragma pack(1)

/*
Another code
*/
#endif

func.h

#ifndef _FUNC_H_
#define _FUNC_H_

typedef struct _st_sample_ {
    char flag;
    int value;
    char ext;
}st_sample, *p_st_sample;

void print_sizeof(p_st_sample p_sample);

#endif


func.c

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

void print_sizeof(p_st_sample p_sample) {
    printf("%s: sizeof(st_sample): %ld\r\n", __FUNCTION__, sizeof(*p_sample));
}

main.c

#include <stdio.h>
#include <stdlib.h>
#include "pack.h"
#include "func.h"

int main(int argc, char* argv[]) { 
    st_sample sample;
    p_st_sample p_sample = &sample;
    printf("%s: sizeof(st_sample):%ld\r\n", __FUNCTION__, sizeof(*p_sample));
    print_sizeof(p_sample);

    return 0;
}
程序编译后,其结果如下:

./a.out 
main: sizeof(st_sample):6
print_sizeof: sizeof(st_sample): 12


可以看到,对同样的数据结构,其sizeof的值是不一样的。这样问题就很明显的显示出来了。

根本原因就是在pack.h中没有注意到#pragma pack(1)关键字的范围,实际上,我们不应该将pack.h中的#pragma pack(1)的作用范围暴露在该头文件之外。应该怎么做了?

修改pack.h文件如下

更正后的pack.h

#ifndef _PACK_H_
#define _PACK_H_

#pragma pack(1)

/*
Another code
*/

#pragma pack()
#endif
编译源代码后,执行结果如下:

./a.out 
main: sizeof(st_sample):12
print_sizeof: sizeof(st_sample): 12





<think>嗯,用户现在问的是关于C语言面试和考研复试的专业课问题。我需要先回顾一下之前的历史对话,看看有没有相关的上下文。之前的对话中,用户询问了类似链表逆置的算法问题,我提供了一些链表相关的题目。现在用户转向C语言的专业课问题,可能是在准备面试或者考研复试,需要更深入的语言特性和概念性的问题。 首先,用户可能希望得到不仅仅是算法题,而是更广泛的C语言知识点,比如指针、内存管理、结构体、文件操作等。这些问题通常是面试和考试中的重点。此外,用户可能想要了解一些常见的陷阱题或者理论题,比如宏定义、预处理器、作用域、存储类别等。 接下来,我需要考虑如何组织这些内容,确保覆盖C语言的核心知识点,同时结构清晰,便于用户理解和复习。可能需要分成几个大类,比如指针与内存管理、数据结构与算法实现、语言特性与语法细节、文件与系统编程、代码阅读与调试、设计模式与工程实践等。 每个大类下要列举典型问题,并给出简要的解析,这样用户能知道考察的重点在哪里。例如,在指针与内存管理部分,可以问指针和数组的区别,const关键字的作用,内存泄漏的原因等。这些问题不仅考察基本概念,还考察实际编程中的问题解决能力。 还要注意用户可能的深层需求,比如他们可能希望了解如何在实际编码中避免常见错误,或者理解某些复杂概念如函数指针、递归的使用场景。因此,在问题解析中需要结合实际例子,说明这些知识点如何应用,以及常见的错误类型。 此外,用户可能对动态内存分配、结构体对齐、位运算等高级话题也有需求,特别是考研复试中可能会涉及更深入的问题。因此,需要涵盖这些内容,但也要注意难易程度,避免过于晦涩。 最后,要提醒用户进行实践练习,比如通过编程题巩固知识,查阅经典教材,或者使用调试工具分析代码。这些建议能帮助用户更好地准备面试和考试,提升他们的实际操作能力。</think>以下是针对C语言面试及考研复试中常见的专业课问题整理,涵盖核心语法、指针、内存管理、数据结构实现等高频考点,帮助系统复习: --- ### **一、指针与内存管理** 1. **指针与数组的区别** - 问题:`int a[10]`中,`a`和`&a`的区别是什么?`sizeof(a)`和`sizeof(int*)`的结果是否相同? - 考察点:数组名在大多数情况下退化为指针,但二者并非完全等价。 2. **动态内存分配** - 问题:`malloc`、`calloc`、`realloc`的区别?如何避免内存泄漏? - 关键点:动态内存的生命周期、野指针问题。 3. **`const`关键字的作用** - 问题:`const int* p`、`int* const p`、`const int* const p`的区别? - 解析:常量指针 vs 指针常量。 --- ### **二、数据结构与算法实现** 4. **链表操作** - 问题:如何判断链表是否有环?如何逆置单链表? - 代码实现:快慢指针法、三指针迭代法。 5. **栈与队列的实现** - 问题:用数组或链表实现栈的结构体定义及关键函数(如`push`, `pop`)。 - 陷阱:数组实现的栈溢出判断。 6. **二叉树遍历** - 问题:非递归实现二叉树的中序遍历(需手写栈结构)。 --- ### **三、语言特性与语法细节** 7. **预处理器与宏** - 问题:`#define SQUARE(x) x*x`是否存在问题?如何改进? - 解析:宏的副作用(如`SQUARE(a+1)`展开错误)。 8. **结构体对齐** - 问题:`struct { char c; int i; double d; }`的大小是多少?如何优化内存占用? - 关键点:`#pragma pack`指令的作用。 9. **`volatile`关键字的作用** - 问题:多线程或硬件编程中为何需要使用`volatile`? --- ### **四、文件与系统编程** 10. **文件操作** - 问题:`fread`与`fwrite`的返回值含义?如何判断文件读取结束? - 答案:返回实际读取/写入的元素个数,需结合`feof`和`ferror`判断状态。 11. **静态库与动态库** - 问题:静态链接(`.a`)和动态链接(`.so`/`.dll`)的区别及优缺点? --- ### **五、代码阅读与调试** 12. **指针与内存错误** - 给出代码片段,分析以下问题: ```c char *str = "hello"; str[0] = 'H'; // 是否合法? ``` - 答案:字符串常量存储在只读段,修改会导致未定义行为。 13. **递归与栈溢出** - 问题:递归实现斐波那契数列的时间复杂度?如何优化? - 解析:$O(2^n)$ vs 迭代法$O(n)$。 --- ### **六、设计模式与工程实践** 14. **模块化编程** - 问题:头文件(`.h`)和源文件(`.c`)的分工?如何避免重复包含? - 技巧:`#ifndef` + `#define` + `#endif`。 15. **回调函数与函数指针** - 问题:如何用函数指针实现回调机制?举例说明(如`qsort`的比较函数)。 --- ### **复习建议** 1. **动手编码**:链表、树、排序算法必须手写实现,注意边界条件。 2. **理解底层原理**:如内存对齐、指针运算、预处理过程。 3. **调试技巧**:使用`gdb`分析段错误、内存泄漏工具(如Valgrind)。 4. **参考书籍**:《C程序设计语言》(K&R)、《C陷阱与缺陷》。 这些题目覆盖了C语言的核心考点,建议结合具体代码实践加深理解!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值