嵌入式系统: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++编程、操作系统原理、数据结构算法、计算机网络技术以及嵌入式软件知识的平台。 在本专栏中,我们将分享关于嵌入式系统开发的最新趋势、实用技巧、行业见解以及面试准备建议。无论您是刚入门的学习者还是经验丰富的专业人士,我们都致力于为您提供有价值的内容,帮助您在嵌入式软件领域取得成功。