面试题答案
一键面试合理分配内存资源避免内存泄漏
- 动态内存分配与释放的配对使用
- 在C语言中,使用
malloc
、calloc
、realloc
等函数分配内存后,必须使用相应的free
函数进行释放。例如:
char *buffer = (char *)malloc(1024); if (buffer!= NULL) { // 使用buffer free(buffer); buffer = NULL; }
- 对于
calloc
分配的内存,同样使用free
释放:
int *array = (int *)calloc(10, sizeof(int)); if (array!= NULL) { // 使用array free(array); array = NULL; }
- 当使用
realloc
调整内存大小时,要注意返回值。如果realloc
成功,会返回新的内存指针,需要用新指针替换旧指针,并释放旧指针(如果旧指针和新指针不同)。例如:
char *new_buffer = (char *)realloc(buffer, 2048); if (new_buffer!= NULL) { buffer = new_buffer; // 使用新的buffer free(buffer); buffer = NULL; } else { // realloc失败处理 }
- 在C语言中,使用
- 内存管理函数的错误处理
- 所有的动态内存分配函数在失败时都会返回
NULL
。因此,每次调用这些函数后都应该检查返回值。例如:
char *buffer = (char *)malloc(1024); if (buffer == NULL) { perror("malloc"); // 处理内存分配失败的情况,比如退出程序或采取其他措施 }
- 所有的动态内存分配函数在失败时都会返回
- 使用智能指针或封装内存管理
- 虽然C语言没有像C++那样原生的智能指针,但可以通过自定义结构体和函数来模拟类似的功能。例如,可以创建一个结构体来管理内存指针及其大小,并编写函数来分配、释放内存。
typedef struct { char *data; size_t size; } MemoryBlock; MemoryBlock *createMemoryBlock(size_t size) { MemoryBlock *block = (MemoryBlock *)malloc(sizeof(MemoryBlock)); if (block!= NULL) { block->data = (char *)malloc(size); if (block->data!= NULL) { block->size = size; return block; } else { free(block); return NULL; } } return NULL; } void freeMemoryBlock(MemoryBlock *block) { if (block!= NULL) { free(block->data); free(block); } }
- 在函数中避免内存泄漏
- 当函数返回动态分配的内存指针时,调用者有责任释放该内存。确保在函数设计时明确这一点。例如:
char *getString() { char *str = (char *)malloc(50); if (str!= NULL) { strcpy(str, "Hello, World!"); } return str; } int main() { char *result = getString(); if (result!= NULL) { printf("%s\n", result); free(result); } return 0; }
文件描述符在多进程间共享时的注意事项
- 文件描述符表的独立性
- 每个进程都有自己独立的文件描述符表。当使用
fork
创建子进程时,子进程会继承父进程的文件描述符表,但这并不意味着它们共享同一个文件描述符对象。父子进程的文件描述符虽然值相同,但它们是各自表中的不同项,指向内核中的同一个打开文件对象。例如:
int fd = open("test.txt", O_RDONLY); if (fd == -1) { perror("open"); return 1; } pid_t pid = fork(); if (pid == -1) { perror("fork"); close(fd); return 1; } else if (pid == 0) { // 子进程 // 可以使用fd,但和父进程的fd是不同表中的项 close(fd); return 0; } else { // 父进程 close(fd); wait(NULL); return 0; }
- 每个进程都有自己独立的文件描述符表。当使用
- 文件偏移量的共享
- 由于父子进程的文件描述符指向同一个打开文件对象,它们共享文件偏移量。如果父进程读取了一部分数据,文件偏移量会移动,子进程再读取时会从父进程移动后的位置开始。例如,在父子进程都对同一个文件进行读写操作时,要注意这种共享偏移量带来的影响。可以使用
lseek
函数来调整文件偏移量,以满足不同的需求。
int fd = open("test.txt", O_RDWR); if (fd == -1) { perror("open"); return 1; } pid_t pid = fork(); if (pid == -1) { perror("fork"); close(fd); return 1; } else if (pid == 0) { // 子进程 lseek(fd, 0, SEEK_SET); char buffer[100]; ssize_t read_bytes = read(fd, buffer, sizeof(buffer)); if (read_bytes > 0) { buffer[read_bytes] = '\0'; printf("Child read: %s\n", buffer); } close(fd); return 0; } else { // 父进程 char write_buffer[] = "Hello from parent"; ssize_t write_bytes = write(fd, write_buffer, strlen(write_buffer)); if (write_bytes!= strlen(write_buffer)) { perror("write"); } close(fd); wait(NULL); return 0; }
- 由于父子进程的文件描述符指向同一个打开文件对象,它们共享文件偏移量。如果父进程读取了一部分数据,文件偏移量会移动,子进程再读取时会从父进程移动后的位置开始。例如,在父子进程都对同一个文件进行读写操作时,要注意这种共享偏移量带来的影响。可以使用
- 文件描述符的关闭
- 为了避免资源泄漏,每个进程在使用完文件描述符后都应该及时关闭。如果父进程在
fork
后继续使用文件描述符,子进程也使用,要确保在各自不再需要时关闭。否则,可能会导致文件描述符耗尽等问题。例如,在上述例子中,父子进程都在使用完fd
后调用close(fd)
。
- 为了避免资源泄漏,每个进程在使用完文件描述符后都应该及时关闭。如果父进程在
- 信号处理与文件描述符
- 当在多进程环境中处理信号时,要注意信号处理函数可能会影响文件描述符的状态。例如,如果信号处理函数中调用了可能会改变文件描述符状态的函数(如
close
),可能会导致主程序逻辑出现问题。在信号处理函数中操作文件描述符时要格外小心,最好遵循异步信号安全的原则。可以通过设置标志位等方式,在主程序中处理文件描述符相关的操作,而不是在信号处理函数中直接操作。
- 当在多进程环境中处理信号时,要注意信号处理函数可能会影响文件描述符的状态。例如,如果信号处理函数中调用了可能会改变文件描述符状态的函数(如
- 跨进程传递文件描述符
- 在一些复杂场景下,可能需要在不同进程间传递文件描述符,例如通过Unix域套接字。这需要使用特殊的机制,如
sendmsg
和recvmsg
函数,并设置相应的控制消息来传递文件描述符。在传递过程中,要确保接收方正确接收和处理文件描述符,并且注意传递过程中的错误处理。例如:
// 发送方代码片段 int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); // 连接到接收方 struct msghdr msg; memset(&msg, 0, sizeof(msg)); struct cmsghdr *cmsg; char cmsgbuf[CMSG_SPACE(sizeof(int))]; int fd_to_send = open("file.txt", O_RDONLY); msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; *((int *)CMSG_DATA(cmsg)) = fd_to_send; // 使用sendmsg发送数据和文件描述符 // 接收方代码片段 int recv_sockfd = socket(AF_UNIX, SOCK_STREAM, 0); // 接受连接 struct msghdr recv_msg; memset(&recv_msg, 0, sizeof(recv_msg)); char recv_cmsgbuf[CMSG_SPACE(sizeof(int))]; recv_msg.msg_control = recv_cmsgbuf; recv_msg.msg_controllen = sizeof(recv_cmsgbuf); // 使用recvmsg接收数据和文件描述符 struct cmsghdr *recv_cmsg = CMSG_FIRSTHDR(&recv_msg); if (recv_cmsg!= NULL && recv_cmsg->cmsg_len == CMSG_LEN(sizeof(int)) && recv_cmsg->cmsg_level == SOL_SOCKET && recv_cmsg->cmsg_type == SCM_RIGHTS) { int received_fd = *((int *)CMSG_DATA(recv_cmsg)); // 使用received_fd close(received_fd); }
- 在一些复杂场景下,可能需要在不同进程间传递文件描述符,例如通过Unix域套接字。这需要使用特殊的机制,如