当前位置: 首页 > news >正文

10.C 语言内存划分,static,字符串

字符串

C语言中,字符串实际上是以空字符 \0 结尾的字符数组。这意味着一个字符串不仅仅包含可见字符,还必须包含一个终止符(\0),这标志着字符串的结束。这种表示方法使得处理字符串既灵活又具有一定的复杂性。

字符串的定义

  1. 常量字符串:用双引号括起来的一组字符。

    char *str = "Hello, World!";

    这里 str 是一个指向字符串字面量的指针,该字符串存储在只读内存区域。

  2. 字符数组:用于存储可修改的字符串。

    char str[] = "Hello, World!";

    或者明确指定大小:

    char str[14] = "Hello, World!";

    注意这里的大小为 14,因为包括了结尾的 \0

字符串的操作 

C语言标准库提供了丰富的字符串处理函数,位于 <string.h> 头文件中:

  • strlen:计算字符串长度(不包括末尾的 \0)。

    size_t len = strlen("Hello");
  • strcpystrncpy:复制字符串。

    • strncpy

    • 功能:与 strcpy 类似,但是增加了额外的参数 n,表示最多复制的字符数。如果源字符串长度小于 n,则在目标字符串后面填充足够的空字符 \0 直到总共写了 n 个字符;如果源字符串长度大于或等于 n,则不会在目标字符串末尾添加终止空字符 \0,这意味着结果可能不是一个有效的C字符串。

    • char dst[20];
      strcpy(dst, "Hello"); // 完全复制
      strncpy(dst, "Hello", 5); // 最多复制前5个字符
  • strcatstrncat:连接字符串。

    char greeting[20] = "Hello, ";
    strcat(greeting, "World!"); // 将"World!"追加到greeting后
  • strcmpstrncmp:比较两个字符串。

    int result = strcmp("abc", "def"); // 如果第一个参数小于第二个参数,则返回负数
  • strstr:查找子串首次出现的位置。

    char *pos = strstr("Hello, World!", "World");

C 语言内存的几个主要区域

  1. 栈 (Stack)

    • 用途:存储函数的局部变量(包括基本数据类型和数组)、函数参数、函数调用的返回地址和控制信息。
    • 管理:由编译器自动管理。当函数被调用时,其局部变量和相关信息被压入栈;当函数返回时,这些数据被自动弹出并释放。
    • 特点:访问速度快,空间有限,遵循后进先出(LIFO)原则。
  2. 堆 (Heap)

    • 用途:用于动态内存分配。程序员使用 malloccallocrealloc 等函数在此区域分配内存,并使用 free 函数显式释放。
    • 管理:由程序员手动管理。如果分配的内存没有被释放,就会导致内存泄漏。
    • 特点:空间相对较大,分配和释放速度比栈慢。
  3. 全局/静态存储区 (Global/Static Storage Area)

    • 这个区域通常进一步细分为两个部分:
      • 已初始化的数据段 (Initialized Data Segment / .data)
        • 用途:存储程序中已初始化的全局变量和静态变量(包括 static 全局变量和 static 局部变量)。
        • 特点:在程序启动时分配,在程序结束时释放。
      • 未初始化的数据段 (Uninitialized Data Segment / .bss - Block Started by Symbol)
        • 用途:存储程序中未初始化的全局变量和静态变量(编译器会自动将它们初始化为 0)。
        • 特点:在程序启动时分配,在程序结束时释放。
  4. 常量存储区 (Constant Storage Area / .rodata - Read-Only Data)

    • 用途:存储程序中的常量数据,例如字符串字面量(String Literals)、用 const 修饰的全局或静态常量(某些实现可能放在这里,也可能放在 .data 段,但字符串字面量几乎总是在 .rodata)。
    • 特点:通常位于只读内存区域,程序试图修改这里的值会导致未定义行为(通常是运行时错误,如段错误)。
  5. 代码区 (Code Segment / Text Segment)

    • 用途:存储程序的可执行指令(机器码)。
    • 特点:通常是只读的,以防止程序意外修改自身的指令。

字符串字面量存在哪个区域?

字符串字面量(例如 "Hello, World!")存储在常量存储区(.rodata 段)

  • 为什么? 字符串字面量是程序的一部分,在编译时就已经确定了内容。将它们放在只读的常量区可以防止程序意外修改它们,并且可以在多个地方安全地共享同一个字符串字面量的实例。
  • 示例
    char *str1 = "Hello";
    char *str2 = "Hello"; // str1 和 str2 可能指向同一个内存地址
    在这个例子中,"Hello" 这个字符串字面量被存储在 .rodata 段。str1 和 str2 都是指向这个只读内存区域的指针。尝试修改 str1[0] = 'h'; 会导致未定义行为。

动态分配字符串

动态分配字符串意味着你需要使用标准库函数 malloccallocrealloc 来在运行时为字符串分配内存。这种方法允许你在程序执行期间根据实际需求调整字符串的大小,而不是在编译时就固定下来。

#include <stdio.h>
#include <stdlib.h>int main() {char *str;// 分配内存用于存储字符串 "Hello, World!"str = (char *) malloc(14 * sizeof(char)); // 13个字符 + 1个'\0'if (str == NULL) { // 检查是否分配成功fprintf(stderr, "内存分配失败\n");exit(1);}// 复制字符串到新分配的内存strcpy(str, "Hello, World!");printf("%s\n", str);// 释放内存free(str);return 0;
}

这里我们使用了 malloc 函数来分配足够的内存以容纳指定的字符串,并通过 strcpy 将字符串复制到新分配的内存位置。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main() {char *str = (char *) malloc(5 * sizeof(char)); // 初始分配5个字符的空间if (str == NULL) {fprintf(stderr, "内存分配失败\n");exit(1);}strncpy(str, "Hi!", 4); // 包含终止符'\0'printf("Initial string: %s\n", str);// 重新分配更多空间str = (char *) realloc(str, 14 * sizeof(char)); // 现在有足够的空间存储 "Hello, World!"if (str == NULL) {fprintf(stderr, "内存重新分配失败\n");exit(1);}strcpy(str, "Hello, World!");printf("Extended string: %s\n", str);free(str);return 0;
}

注意:当使用 realloc 时,如果它不能分配请求的额外内存,则返回 NULL 并保持原内存块不变。因此,在重新分配之后检查返回值是很重要的,以避免丢失原始数据。

注意事项

  • 初始化:动态分配的内存不一定被初始化为零或空字符。如果你打算立即使用这块内存作为字符串,请确保它以 \0 结尾。
  • 内存泄漏:每次调用 malloccalloc 或 realloc 后,都应该有相应的 free 调用来释放分配的内存,否则会导致内存泄漏。
  • 越界访问:确保不会超出分配的内存范围写入数据,这可能会导致未定义行为或程序崩溃。

静态局部变量

static 关键字用于局部变量声明时,它改变了该变量的生命周期但不改变其作用域。具体来说:

  • 生命周期:静态局部变量在整个程序运行期间都存在(即从程序开始到结束),而不是仅仅在其所在的代码块执行期间存在。
  • 初始化:静态局部变量只会在第一次进入其所在的代码块时被初始化一次,并且如果没有显式初始化,会被自动初始化为0(对于数值类型)或NULL(对于指针类型)。
  • 作用域:尽管其生命周期是整个程序运行期间,但它的作用域仍然局限于声明它的代码块内。
void func() {static int count = 0; // 只有第一次调用func时才会初始化为0count++;printf("%d\n", count);
}

每次调用 func() 函数时,count 的值都会增加,而不是重新从0开始计数。

int main() {func();func();return 0;
}

 

数字转换为字符串

使用 sprintf 或 snprintf

sprintfsnprintf 函数可以用来格式化输出到一个字符数组中,非常适合用于数字到字符串的转换。

  • sprintf:不检查目标缓冲区大小,可能导致缓冲区溢出。
  • snprintf:更安全的选择,允许指定最大写入字符数(包括终止空字符)。
#include <stdio.h>int main() {char buffer[10]; // 确保有足够的空间int num = 12345;// 使用 sprintfsprintf(buffer, "%d", num);printf("Using sprintf: %s\n", buffer);// 使用 snprintf 更加安全snprintf(buffer, sizeof(buffer), "%d", num);printf("Using snprintf: %s\n", buffer);return 0;
}
使用 itoa(非标准)

尽管一些编译器支持 itoa 函数用于整数到字符串的转换,但它不是标准 C 库的一部分,因此移植性较差。

#include <stdio.h>int main() {char buffer[10];int num = 12345;itoa(num, buffer, 10); // 将整数num转换为十进制字符串存入bufferprintf("Using itoa: %s\n", buffer);return 0;
}

注意:由于 itoa 不是标准函数,使用时需谨慎。

字符串转换为数字

C语言提供了几个函数用于将字符串转换为各种类型的数字值:

使用 atoiatolatof
  • atoi: 将字符串转换为 int 类型。
  • atol: 将字符串转换为 long 类型。
  • atof: 将字符串转换为 double 类型。

这些函数简单易用,但不会报告转换错误。

#include <stdio.h>
#include <stdlib.h>int main() {const char *strInt = "12345";const char *strFloat = "123.45";int intValue = atoi(strInt);double floatValue = atof(strFloat);printf("Integer value: %d\n", intValue);printf("Float value: %.2f\n", floatValue);return 0;
}
使用 strtolstrtoulstrtod

对于需要更多控制的情况,比如想要检测转换错误或处理基数不同的数字(如二进制、八进制、十六进制),可以使用以下函数:

  • strtol: 将字符串转换为长整型,并提供更多的错误检查功能。
  • strtoul: 类似于 strtol,但是结果是无符号长整型。
  • strtod: 将字符串转换为双精度浮点数。
#include <stdio.h>
#include <stdlib.h>int main() {const char *strInt = "12345";char *end;long int li = strtol(strInt, &end, 10); // 10表示十进制if (*end != '\0') { // 检查是否全部转换成功printf("Conversion error!\n");} else {printf("Converted integer: %ld\n", li);}return 0;
}

这些函数不仅能够进行转换,还能通过 end 参数返回第一个无法被转换的字符位置,从而帮助判断转换是否完全成功。

总结

无论是将数字转换为字符串还是反之,选择合适的方法取决于你的具体需求。对于简单的场景,sprintf/snprintfatoi 系列函数通常足够了;而对于需要更多控制或者更高的安全性要求的场合,则应该考虑使用 strtolstrtoulstrtod 等函数。

 

http://www.dtcms.com/a/307491.html

相关文章:

  • MFC CChartCtrl编程
  • 逻辑回归的应用
  • 【人工智能】当AI智能体遇上安全与伦理:一场技术与人性的对话
  • 3DXML 转换为 UG 的技术指南及迪威模型网在线转换推荐
  • arm架构系统打包qt程序--麒麟操作系统为例
  • 递归混合架构(MoR)在医疗领域的发展应用能力探析
  • 网络编程(一)TCP编程和UDP编程
  • Kubernetes集群中滚动更新失败与资源配置错误的深度解析及应对策略
  • 机器学习03——数据与算法初步2
  • Git之本地仓库管理
  • 第一篇:【Python-geemap教程(三)上】3D地形渲染与Landsat NDVI计算
  • 学习 java web 简单监听器
  • 《能碳宝》AI辅助开发系统方案
  • ES 工业网关:比德国更适配,比美国更易用
  • 编程语言Java——核心技术篇(六)解剖反射:性能的代价还是灵活性的福音?
  • Ubuntu/Debian 搭建 Nginx RTMP 服务器全攻略
  • 使用的IDE没有内置MCP客户端怎么办?
  • [源力觉醒 创作者计划]_文心4.5开源测评:国产大模型的技术突破与多维度能力解析
  • 数据库中使用SQL作分组处理01(简单分组)
  • Web3.0 和 Web2.0 生态系统比较分析:差异在哪里?
  • Web3:在 VSCode 中使用 Vue 前端与已部署的 Solidity 智能合约进行交互
  • Kotlin -> 普通Lambda vs 挂起Lambda
  • Astra主题WooCommerce如何添加可变产品Astra variation product
  • tplink er2260t配置vlan透传iptv
  • python学智能算法(二十九)|SVM-拉格朗日函数求解中-KKT条件理解
  • 数据结构: 双向列表
  • 银河麒麟桌面操作系统:自定义截图快捷键操作指南
  • NXP i.MX8MP GPU 与核心库全景解析
  • rapidocr_web v1.0.0发布了
  • 旧物重生,交易有温度——旧物回收二手交易小程序,让生活更美好