嵌入式系统: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;
  1. const int* p;  这表示指针 p 指向一个常量整数。这意味着你不能通过 p 修改所指向的整数的值,但是你可以改变 p 指向另一个整数。
  2. int* const p; 这表示 p 是一个指向整数的常量指针。这意味着你可以通过 p 修改所指向的整数的值,但是你不能改变 p 指向另一个整数。
  3. int const *p; 这和第一个声明 const int* p; 是等价的,表示指针 p 指向一个常量整数。
  4. const int* const p; 这表示 p 是一个指向常量整数的常量指针。这意味着你既不能通过 p 修改所指向的整数的值,也不能改变 p 指向另一个整数。

9.如果有一个地址是0X5566,如何在这个地址赋值成10?

 int *ptr = (int *)0x5566; // 将指针ptr指向地址0x5566
  *ptr = 10; // 在地址0x5566处赋值为10

10.malloc的底层原理

1.结论:

  1. 当开辟的空间小于 128K 时,调用 brk()函数,malloc 的底层实现是系统调用函数 brk(),其主要移动指针 _enddata(此时的 _enddata 指的是 Linux 地址空间中堆段的末尾地址,不是数据段的末尾地址)
  2. 当开辟的空间大于 128K 时,mmap()系统调用函数来在虚拟地址空间中(堆和栈中间,称为“文件映射区域”的地方)找一块空间来开辟。

2.具体实现

  1. 当调用 malloc(size) 时,它首先计算需要分配的内存块大小,包括用户请求的大小以及内存管理所需的额外空间(例如内存块的管理信息)。
  2. malloc 会遍历一个数据结构(例如空闲链表或空闲块列表),查找合适大小的空闲内存块。
  3. 如果找到了合适的内存块,malloc 会将其标记为已分配,并返回一个指向该内存块的指针给用户。
  4. 如果没有足够大的空闲内存块可用,malloc 可能需要扩展程序的虚拟内存空间。它通过系统调用(例如 brk 或 mmap)向操作系统请求更多的连续内存空间。
  5. 当操作系统提供了更多的内存空间后,malloc 可以从新的空间中分配出合适大小的内存块,并将其标记为已分配。
  6. 在内存块被释放时,通过调用 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++编程、操作系统原理、数据结构算法、计算机网络技术以及嵌入式软件知识的平台。 在本专栏中,我们将分享关于嵌入式系统开发的最新趋势、实用技巧、行业见解以及面试准备建议。无论您是刚入门的学习者还是经验丰富的专业人士,我们都致力于为您提供有价值的内容,帮助您在嵌入式软件领域取得成功。

全部评论

相关推荐

评论
3
11
分享

创作者周榜

更多
牛客网
牛客企业服务