面经--C语言——sizeof和strlen,数组和链表,#include <>和 #include ““ #define 和typedef 内存对齐概述

发布于:2025-02-10 ⋅ 阅读:(56) ⋅ 点赞:(0)

sizeofstrlen

特性 sizeof strlen
定义 运算符,也是关键字,用于获取类型或变量所占的字节数 函数,用于计算 C 风格字符串的长度(不包括 '\0'
用途 返回对象或数据类型在内存中占用的字节数 返回 C 风格字符串的字符数量(不包括 '\0'
计算方式 在编译时确定,求值过程是静态的 在运行时计算,需遍历字符串逐字符计数
适用类型 适用于所有类型,包括数组、结构体、指针、基本数据类型等 仅适用于 C 风格字符串(以 '\0' 结尾的字符数组)
返回值 返回的是字节数,通常是 size_t 类型 返回字符串中字符的数量,不包括 '\0' 字符
操作对象 数据类型、变量、数组、结构体等 C 风格字符串(const char*char[]
是否包括 '\0' 包括 '\0'(例如对字符数组使用 sizeof 会计算整个数组大小) 不包括 '\0'(只计算实际字符数)
适用场景 获取类型或数组的内存大小,计算静态数据结构的大小 获取字符串的实际长度(不包括结束符 '\0'
示例代码 sizeof(int) 返回 4(通常情况下) strlen("Hello") 返回 5
计算复杂度 常数时间复杂度,编译时计算 线性时间复杂度,需要遍历整个字符串

数组和链表

特性 数组 链表
定义 数组是一种线性数据结构,它包含固定大小的连续元素 链表是一种线性数据结构,它由节点组成,每个节点包含数据和指向下一个节点的指针
内存结构 在内存中分配一个连续的块 每个节点在内存中不一定是连续的,节点通过指针连接
存储方式 静态存储方式(大小固定) 动态存储方式(可以动态增加或删除节点)
元素类型 所有元素类型相同(通常是基本数据类型) 每个节点可以存储不同类型的数据,节点可以包含不同的结构
访问方式 通过索引直接访问元素,时间复杂度为 O(1) 通过指针遍历,每次只能访问当前节点,时间复杂度为 O(n)
插入与删除 插入或删除元素时需要移动元素,时间复杂度为 O(n) 插入和删除操作在头部和尾部的时间复杂度为 O(1),在中间位置为 O(n)
内存分配 在定义数组时就分配固定大小的内存 节点可以在运行时动态分配和释放内存
大小变动 大小固定,无法在运行时调整 可以动态增减节点,大小可以灵活变化
内存浪费 如果数组大小过大,可能会浪费内存 如果链表很长且有很多空节点,可能会浪费指针的内存
优缺点 优:访问快速;缺:插入和删除操作不灵活 优:插入和删除操作灵活;缺:访问速度较慢

总结

  • 数组 是一种高效的、固定大小的线性数据结构,适合需要快速访问的场景,但它不适合频繁的插入和删除操作。
  • 链表 适合需要频繁动态插入和删除的场景,尽管它的访问速度较慢,但它提供了更大的灵活性,尤其在内存使用方面。

#include <>和 #include “”

特性 #include <> #include ""
用途 用于包含系统或标准库的头文件 用于包含用户定义的头文件或当前目录下的头文件
查找顺序 编译器首先查找标准库路径中的头文件 编译器首先查找当前目录中的头文件,若找不到,再查找标准库路径
适用场景 用于包含标准库或第三方库的头文件(如 #include <iostream> 用于包含项目中的自定义头文件(如 #include "myheader.h"

#define 和typedef

#definetypedef 都是 C/C++ 中常用的预处理指令和关键字,主要用于定义常量、宏和类型别名。它们有不同的功能和使用方式,下面是详细的比较:

特性 #define typedef
用途 用于定义宏(常量、函数、代码片段) 用于定义类型别名(为现有类型起别名)
作用范围 在宏定义之后,它的作用范围是整个文件 在定义之后,它的作用范围通常是当前作用域或文件
类型检查 不进行类型检查,纯粹的文本替换 进行类型检查,确保新类型的合法性
宏定义的作用 定义常量、简单函数或复杂的代码块 不支持宏功能,只有类型别名
宏展开时机 宏在预处理阶段展开(编译前) typedef 只是定义别名,不会进行宏展开
调试支持 宏没有调试信息(因为宏只是简单的文本替换) typedef 可以像普通类型一样调试
示例 #define PI 3.14 typedef int Integer;

内存对齐概述

内存对齐(Memory Alignment)是计算机体系结构中对数据存储方式的一种要求,它规定了数据类型的存储地址必须是某个特定值的倍数。

在现代计算机中,通常要求数据按照一定的对齐方式存储,例如 4 字节对齐、8 字节对齐等。对齐通常会影响结构体和数组的内存布局,从而决定其内存占用的大小。

对齐规则

  1. 基本对齐规则

    • 每个数据类型的存储地址应当是该数据类型大小的倍数。例如:
      • char 类型通常是 1 字节,通常可以存储在任何地址。
      • int 类型通常是 4 字节,存储地址必须是 4 的倍数。
      • double 类型通常是 8 字节,存储地址必须是 8 的倍数。
  2. 结构体对齐

    • 对结构体中的成员进行对齐时,结构体的总大小将是最大成员对齐要求的倍数。
    • 结构体中每个成员的起始地址需要满足其对齐要求。如果某个成员的起始地址不符合其对齐要求,编译器会在它和下一个成员之间插入填充字节(padding bytes)来保证对齐。
  3. 结构体总大小

    • 结构体的总大小应该是最大对齐要求的倍数。如果结构体中的成员总和大小不满足最大对齐要求,编译器会在结构体末尾添加填充字节,以使结构体的总大小是对齐要求的倍数。

示例:结构体的内存对齐

考虑以下代码:

#include <stdio.h>

typedef struct {
    char c;    // 1 字节
    int i;     // 4 字节
    double d;  // 8 字节
} Example;
分析:
  • char c 占 1 字节。
  • int i 占 4 字节。
  • double d 占 8 字节。

Example 结构体中:

  • char c 需要 1 字节,但是由于 int 需要 4 字节对齐,编译器会在 char c 后插入 3 个填充字节,使 int i 能够正确地以 4 字节对齐。
  • int i 占 4 字节,按照 4 字节对齐。
  • double d 占 8 字节,按照 8 字节对齐。
  • 结构体的总大小应是 8 的倍数(由于 double 的 8 字节对齐要求)。因此,结构体的总大小会扩展到 24 字节(1 + 3 + 4 + 8 + 8 的填充)。

结果:该结构体的大小是 24 字节,而不是成员大小的总和 13 字节。

内存对齐的常见规则:

数据类型 对齐方式 描述
char 1 字节 char 类型的对齐方式为 1 字节。
short 2 字节 short 类型的对齐方式为 2 字节。
int 4 字节 int 类型的对齐方式为 4 字节。
float 4 字节 float 类型的对齐方式为 4 字节。
double 8 字节 double 类型的对齐方式为 8 字节。
long 4 字节或8字节 long 类型的对齐方式依赖于系统架构。
long long 8 字节 long long 类型的对齐方式为 8 字节。

填充字节的计算

考虑以下结构体定义:

typedef struct {
    char c;    // 1 字节
    int i;     // 4 字节
} MyStruct;
  1. char c 占 1 字节,紧接着是 int i,但是 int 类型要求 4 字节对齐。因此,编译器会在 char 后插入 3 个填充字节,使 int 的起始地址是 4 的倍数。

  2. 所以,int i 占 4 字节,整个结构体的大小需要为 4 字节对齐。因此,结构体的总大小将是 8 字节。

对齐影响的实际例子

#include <stdio.h>

typedef struct {
    char c;
    int i;
    double d;
} Example;

int main() {
    printf("Size of Example: %zu\n", sizeof(Example));
    return 0;
}

假设:

  • char c 占 1 字节。
  • int i 占 4 字节(要求 4 字节对齐)。
  • double d 占 8 字节(要求 8 字节对齐)。

计算结构体内存:

  1. char c 占 1 字节。
  2. 插入 3 字节填充,使 int i 能够 4 字节对齐。
  3. int i 占 4 字节。
  4. double d 占 8 字节,且由于对齐要求,double 的起始地址必须是 8 的倍数。
  5. 结构体的总大小会被扩展到 16 字节,以满足 8 字节对齐。

输出结果

Size of Example: 16

以最大类型所占内存分配内存空间。


网站公告

今日签到

点亮在社区的每一天
去签到