嵌入式系统:C语言基础(二)
1.sizeof与strlen的区别
sizeof:
- 用于获取数据类型或变量的字节大小。
- 可以接受多种参数,包括数据类型、变量名、数组名等。
- 返回的是整个数据类型或变量占用的内存空间大小。
strlen:
- 用于获取以’\0’结尾的字符串的实际长度。
- 在运行时计算,需要遍历字符串的内容来确定长度。
- 返回的是字符串中的字符个数,不包括字符串结束符’\0’。
举例:
char str[] = "Hello"; size_t size_str = sizeof(str); size_t length_str = strlen(str); // size_str 的值为 6,因为包括字符串 "Hello" 的 5 个字符和结尾的 '\0',共 6 个字节 // length_str 的值为 5,因为字符串 "Hello" 有 5 个字符,不包括结尾的 '\0'
注意事项:
- sizeof返回的是静态的大小,而strlen返回的是实际的字符串长度。
- 在使用strlen时要确保操作的对象是以’\0’结尾的字符串,否则可能出现不确定的结果。
- sizeof可以用于任何数据类型或变量,而strlen只适用于字符串。
2.结构体和共用体的区别:
结构体(struct)和共用体(union)是在C和C++等编程语言中用来组织和存储数据的重要概念,它们之间有几个关键的区别:
1.结构体(struct):
- 结构体是一种用户自定义的数据类型,可以包含多个不同类型的成员变量。
- 结构体的各个成员在内存中是按照定义的顺序依次存储的,每个成员有自己的内存空间。
- 结构体的大小等于其所有成员变量大小之和,可能会有内存对齐的问题。
- 结构体的各个成员可以同时被访问和操作。
2.共用体(union):
- 共用体是一种特殊的数据结构,所有成员共享同一块内存空间。
- 共用体的大小等于其最大成员的大小,因为共用体中只有一个成员可以被同时使用。
- 当一个共用体的成员被赋值后,其他成员的值会被覆盖,因为它们共享同一块内存空间。
- 共用体通常用于节省内存空间,或者在需要在不同类型的数据之间进行转换时使用。
3.一个指针可以是volatile吗
一个指针可以被声明为volatile。在C和C++中,volatile是一个关键字,用于告诉编译器不要对声明为volatile的变量进行优化,因为这些变量的值可能在程序的控制之外被改变。
当一个指针被声明为volatile时,意味着指针所指向的数据是volatile的,即这些数据可能会在程序的执行过程中被外部因素改变,如硬件中断、多线程操作等。因此,编译器不应该对这些数据的读写进行优化,而应该每次都从内存中读取或写入数据。
示例:
volatile int *ptr; // 声明一个指向volatile int的指针
int main() {
int a = 10;
ptr = &a;
// 在程序的其他地方可能会改变a的值,编译器不能对ptr所指向的数据做优化
// 因此,每次访问*ptr时都应该从内存中读取a的值
int b = *ptr; // 从内存中读取a的值
}
4.数组名与指针的区别?
数组名:
- 是一个常量指针,指向数组的首元素的地址。
- 大小固定为整个数组的大小,因为数组名代表整个数组。
- 无法被改变或重新赋值,因为数组名是常量指针。
- 无法进行指针运算,因为数组名是常量指针,不允许改变其指向的地址。
指针:
- 是一个变量,存储一个内存地址,可以指向任意类型的对象。
- 大小固定为指针类型的大小,通常与机器的字长相关。
- 可以被改变或重新赋值,可以指向不同的内存地址。
- 可以进行指针运算,如加法、减法等,用来移动指针指向的位置。
5.数组指针与指针数组的区别?
1.数组指针(Pointer to an Array):定义:数组指针是指向数组的指针,它指向整个数组。声明:int (*ptr)[size];,这里ptr是一个指针,指向一个大小为size的数组。用法:可以通过数组指针来访问整个数组,可以使用指针算术运算来访问数组中的元素。
int arr[3] = {1, 2, 3};
int (*ptr)[3] = &arr; // 定义一个指向大小为3的数组的指针
2.指针数组(Array of Pointers):定义:指针数组是一个数组,其中的每个元素都是一个指针。声明:int *arr[size];,这里arr是一个包含size个指针的数组。用法:指针数组中的每个元素都可以指向不同的内存位置,每个指针可以指向不同类型或不同大小的数据。
int a = 1, b = 2, c = 3;
int *ptrArr[3] = {&a, &b, &c}; // 定义一个包含3个指针的数组
总结:
- 数组指针是指向整个数组的指针,用于访问整个数组。
- 指针数组是一个数组,其中的每个元素都是指针,用于存储指向不同位置或不同类型的数据的指针。
6.常见变量定义以及解释:
- int a; // 定义一个变量 a,类型为 int
- int *ptr_a;// 定义一个指针 a,指向 int 类型的变量
- int **ptr_ptr_a;// 定义一个指针 a,指向一个指向 int 类型的指针
- int arr[10];// 定义一个数组 a,有 10 个元素,每个元素是 int 类型
- int *ptr_arr[10];// 定义一个数组 a,有 10 个元素,每个元素是 int 类型的指针
- int (*ptr_to_arr)[10];// 定义一个指针 a,指向一个数组,该数组有 10 个元素,每个元素是 int 类型
- int (*ptr_to_func)(int);// 定义一个指针 a,指向一个参数是 int,返回值是 int 的函数
- int (*arr_of_func[10])(int);// 定义一个数组 a,每个元素是一个指向参数是 int,返回值是 int 的函数指针
7.malloc和calloc的区别
1.参数不同:
- malloc:void *malloc(size_t size),malloc 函数接受一个参数,即需要分配的内存大小(以字节为单位)
- calloc:void *calloc(size_t num, size_t size),calloc 函数接受两个参数,第一个参数是需要分配的元素个数,第二个参数是每个元素的大小(以字节为单位)。
2.内存内容:
- malloc:分配的内存中的内容是未初始化的,可能包含任意值。
- calloc:分配的内存中的内容被初始化为零(所有位都是 0)。
3.返回值:
- malloc 和 calloc 都返回一个指向分配内存起始位置的指针,如果分配失败,则返回 NULL。
4.性能:
- 在某些系统上,calloc 可能比 malloc 稍慢,因为 calloc 在分配内存后会将内存初始化为零。
使用示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr_malloc, *ptr_calloc;
int size = 5;
// 使用 malloc 分配内存
ptr_malloc = (int *)malloc(size * sizeof(int));
if (ptr_malloc == NULL) {
printf("Memory allocation failed with malloc.\n");
return 1;
}
// 使用 calloc 分配内存
ptr_calloc = (int *)calloc(size, sizeof(int));
if (ptr_calloc == NULL) {
printf("Memory allocation failed with calloc.\n");
return 1;
}
// 输出 malloc 分配的内存内容
printf("Memory allocated with malloc:\n");
for (int i = 0; i < size; i++) {
printf("%d ", ptr_malloc[i]);
}
printf("\n");
// 输出 calloc 分配的内存内容
printf("Memory allocated with calloc:\n");
for (int i = 0; i < size; i++) {
printf("%d ", ptr_calloc[i]);
}
printf("\n");
// 释放内存
free(ptr_malloc);
free(ptr_calloc);
return 0;
}
8.说说下面表达式的区别
1. const int* p; 2. int* const p; 4. int const *p; 5. const int* const p;
- const int* p; 这表示指针 p 指向一个常量整数。这意味着你不能通过 p 修改所指向的整数的值,但是你可以改变 p 指向另一个整数。
- int* const p; 这表示 p 是一个指向整数的常量指针。这意味着你可以通过 p 修改所指向的整数的值,但是你不能改变 p 指向另一个整数。
- int const *p; 这和第一个声明 const int* p; 是等价的,表示指针 p 指向一个常量整数。
- const int* const p; 这表示 p 是一个指向常量整数的常量指针。这意味着你既不能通过 p 修改所指向的整数的值,也不能改变 p 指向另一个整数。
9.如果有一个地址是0X5566,如何在这个地址赋值成10?
int *ptr = (int *)0x5566; // 将指针ptr指向地址0x5566 *ptr = 10; // 在地址0x5566处赋值为10
10.malloc的底层原理
1.结论:
- 当开辟的空间小于 128K 时,调用 brk()函数,malloc 的底层实现是系统调用函数 brk(),其主要移动指针 _enddata(此时的 _enddata 指的是 Linux 地址空间中堆段的末尾地址,不是数据段的末尾地址)
- 当开辟的空间大于 128K 时,mmap()系统调用函数来在虚拟地址空间中(堆和栈中间,称为“文件映射区域”的地方)找一块空间来开辟。
2.具体实现
- 当调用 malloc(size) 时,它首先计算需要分配的内存块大小,包括用户请求的大小以及内存管理所需的额外空间(例如内存块的管理信息)。
- malloc 会遍历一个数据结构(例如空闲链表或空闲块列表),查找合适大小的空闲内存块。
- 如果找到了合适的内存块,malloc 会将其标记为已分配,并返回一个指向该内存块的指针给用户。
- 如果没有足够大的空闲内存块可用,malloc 可能需要扩展程序的虚拟内存空间。它通过系统调用(例如 brk 或 mmap)向操作系统请求更多的连续内存空间。
- 当操作系统提供了更多的内存空间后,malloc 可以从新的空间中分配出合适大小的内存块,并将其标记为已分配。
- 在内存块被释放时,通过调用 free 函数,malloc 将其标记为未分配,并将该内存块添加到空闲内存块的列表中,以便后续的内存分配可以重复使用它们。
3.简易代码实现:
#include <unistd.h> // 包含系统调用相关的头文件
typedef struct Block {
size_t size; // 内存块的大小
struct Block* next; // 指向下一个内存块的指针
} Block;
Block* freeList = NULL; // 空闲链表的头指针
void* malloc(size_t size) {
// 检查参数是否合法
if (size <= 0) {
return NULL;
}
// 计算需要分配的内存大小
size_t blockSize = sizeof(Block) + size;
// 在空闲链表中查找符合要求的内存块
Block* prevBlock = NULL;
Block* currBlock = freeList;
while (currBlock != NULL) {
if (currBlock->size >= blockSize) {
// 找到合适大小的空闲块
if (prevBlock != NULL) {
// 删除这个空闲块
prevBlock->next = currBlock->next;
} else {
// 这个空闲块是链表的头节点
freeList = currBlock->next;
}
// 返回指向内存块的指针
return (void*)(currBlock + 1);
}
prevBlock = currBlock;
currBlock = currBlock->next;
}
// 没有找到可用的内存块,请求更多内存空间
Block* newBlock = sbrk(blockSize);
if (newBlock == (void*)-1) {
return NULL; // 请求失败,返回 NULL
}
// 返回指向新内存块的指针
return (void*)(newBlock + 1);
}
void free(void* ptr) {
// 检查参数是否合法
if (ptr == NULL) {
return;
}
// 获取指向内存块起始位置的指针
Block* block = ((Block*)ptr) - 1;
// 将内存块标记为未分配状态,然后将其添加到空闲链表中
block->next = freeList;
freeList = block;
}
#一人推荐一个机械人值得去的公司##面试##牛客在线求职答疑中心##我的实习求职记录##23届找工作求助阵地#欢迎来到《嵌入式知识图谱》专栏!这里是一个专注于涵盖C/C++编程、操作系统原理、数据结构算法、计算机网络技术以及嵌入式软件知识的平台。 在本专栏中,我们将分享关于嵌入式系统开发的最新趋势、实用技巧、行业见解以及面试准备建议。无论您是刚入门的学习者还是经验丰富的专业人士,我们都致力于为您提供有价值的内容,帮助您在嵌入式软件领域取得成功。
查看7道真题和解析