C语言思考题

思考题一

c语言中,什么是字节对齐?如何在代码中实现字节对齐?

在结构体定义中存在字节对齐的问题,这是因为某些早期的CPU架构并不支持奇数地址的访问或者偶数地址访问的速度比奇数地址访问的速度快
所以结构体定义的变量开始位置会对齐到偶数的地址上,而关于结构体中每个变量的偏移地址也会进行“对齐”,C语言中默认时4字节对齐
也可以对其进行修改

1
2
#pragma pack(N)     // N必须2的(0-4)次幂,即为1,2,4,8,16
#pragma pack() // 恢复之前的字节对齐设置

每个变量偏移地址必须为min(N, 结构体变量的大小)的整数倍

C语言结构体字节对齐规则

  • 规则1 :结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存放在offset为该数据成员大小的整数倍的地方(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。

  • 规则2:如果一个结构体B里嵌套另一个结构体A,则结构体A应从offset为min(A内部最大成员, 对齐字节)的整数倍的地方开始存储。(struct B里存有struct A,A里有char,int,double等成员,那A应该从8的整数倍开始存储。),结构体A中的成员的对齐规则仍满足原则1、原则2。

    Tips:结构体A所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。而不是直接将结构体A的成员直接移动到结构体B中

  • 规则3:结构体的总大小,也就是sizeof的结果,必须是min(内部最大成员的整数倍, 对齐字节),不足的要补齐。

思考题二

c语言里面指针函数和函数指针的区别,代码中如何使用?

指针函数本质上是一个返回指针的函数,仅仅是一个函数,返回类型为指针类型,例如malloc

函数指针是一个指向函数的指针。它的类型决定了它指向的函数的参数类型和返回类型。函数名本身就可作为函数指针作为其他函数的传入参数,例如 pthread_create 创建线程时就需要一个void () (void *) 的函数指针作为传入参数

思考题三

c语言进程运行过程中,如何使用gdb进行实时调试,获取变量值?如果进程产生core,如何定位core的位置?明确core的具体原因?

想要调试正在运行的进程,首先你要找到想要调试的进程的进程号,确保该进程编译的时候带-g选项,有调试信息

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 定义全局变量
volatile int counter = 0;

int main()
{
while (1)
{
counter++;
sleep(10); // 每10s加一
}

return EXIT_SUCCESS;
}
1
2
3
4
5
6
7
8
9
10
gcc test5.c -o test5 -g -Wall
./test5 & # 后台执行
ps -e | grep test5
# 输出信息
# 8453 pts/9 00:00:00 test5
gdb -p 8453
break test5.c:12 # 在源码第12行打断点
continue # 代码继续执行直到下一次遇到断点
print counter # 打印变量
detach # 让程序继续运行,但是不再由GDB控制

要让C语言程序生成核心转储(core dump)文件,你需要确保系统配置允许生成(ulimit -a)

ulimit

例子

1
2
3
4
5
6
7
8
#include <stdio.h>

int main()
{
int *ptr = NULL;
*ptr = 10; // 这里会导致段错误
return 0;
}
1
2
3
4
5
6
whc@WHC:~/code/tmp/codeTest/test$ ulimit -c unlimited 
whc@WHC:~/code/tmp/codeTest/test$ ulimit -c
unlimited
whc@WHC:~/code/tmp/codeTest/test$ gcc test6.c -o test6 -Wall
whc@WHC:~/code/tmp/codeTest/test$ gdb ./test6
Segmentation fault (core dumped)

生成核心转储文件(coredump)通常需要满足以下条件:

  • 进程必须有足够的权限:通常,只有进程的拥有者或root用户才能生成核心转储文件。
  • 系统内核参数:/proc/sys/kernel/core_pattern 决定了核心转储文件如何命名和存储。默认情况下,生成的核心转储文件可能不会出现在用户目录中。
1
2
3
4
5
6
7
8
9
10
11
whc@WHC:~/code/tmp/codeTest/test$ sudo cat /proc/sys/kernel/core_pattern
/mnt/wslg/dumps/core.%e
whc@WHC:~/code/tmp/codeTest/test$ ll /mnt/wslg/dumps/
total 124
drwxrwxrwx 2 root root 60 Mar 26 22:07 ./
drwxrwxrwt 8 root root 320 Mar 26 21:21 ../
-rw------- 1 whc whc 307200 Mar 26 22:07 core.test6.23913
cd /mnt/wslg/dumps/
su
gdb /home/whc/code/tmp/codeTest/test/test6 core.test6.23913
bt # bt命令来查看堆栈跟踪

在一个函数中char *p = “hello”;与char p[] = “hello”;的区别

  • char *p = “hello”;
    这是一个指向字符的指针的声明。
    p 是一个指向常量字符串 “hello” 的指针。
    “hello” 是一个常量字符串,存储在只读内存区域。
    你不能通过 p 来修改 “hello” 中的字符,因为它是只读的。
    例如,p[0] = ‘H’; 是非法的,因为这会尝试修改只读内存。

  • char p[] = “hello”;
    这是一个字符数组的声明。
    p 是一个有 6 个元素的数组,包含字符 ‘h’, ‘e’, ‘l’, ‘l’, ‘o’, 和字符串结束符 ‘\0’。
    数组 p 存储在堆栈上,是可读写的。
    你可以通过数组索引来修改数组中的字符,例如 p[0] = ‘H’; 是合法的。

int(*Func())[10] 和 int (*a[5])()

int(*Func())[10]; 这行代码声明了一个函数指针Func。这个函数指针指向的函数返回一个指向包含10个整数的数组的指针。具体来说:

int(*)[] 表示返回一个指向数组的指针,这个数组的内容类型是int。
[10] 表示这个数组有10个整数。
所以,Func是一个指向函数的指针,这个函数返回一个整型数组的指针。简而言之,Func是一个函数指针,它指向的函数返回一个整型数组。

int (*a[5])(); 这行代码声明了一个数组a,这个数组有5个元素,每个元素是一个函数指针。具体来说:

a[5] 表示这是一个有5个元素的数组。
* 表示数组的元素是指针。
() 表示这些指针指向的函数。
int 表示这些函数返回一个整数。
因此,a是一个由5个函数指针组成的数组,每个指针指向一个返回整数类型的函数。


C语言思考题
http://example.com/2024/03/26/C语言思考题/
作者
WHC
发布于
2024年3月26日
许可协议