Skip to content

Latest commit

 

History

History
169 lines (110 loc) · 7.27 KB

sec06.md

File metadata and controls

169 lines (110 loc) · 7.27 KB

主页:教程介绍,上一节:第05节,下一节:第07节

6 字符串和内存管理

GtkTextView 和 GtkTextBuffer 具有一些使用字符串做为参数的函数,和一些返回字符串的函数,字符串和内存管理的知识有助于理解如何使用这些函数。

字符串和内存

字符串是一个以空字符 '\0' 结尾的字符数组,字符串不是 C 类型(例如 char、int、float 或 double),我们通常使用指向字符数组的指针来表示字符串。所以,这个指针通常被称为“字符串”。

在下面的程序中,ab 定义为字符数组,并且是字符串:

char a[10], *b;

a[0] = 'H';
a[1] = 'e';
a[2] = 'l';
a[3] = 'l';
a[4] = 'o';
a[5] = '\0';

b = a;
/* *b is 'H' */
/* *(++b) is 'e' */

数组 a 保存了 char 类型的元素,大小为 10,前六个元素是'H'、'e'、'l'、'l'、'o'和'\0',该数组表示字符串“Hello”,前五个元素是对应于字符的字符码,第六个元素是'\0',值为零,表示字符串在这个位置结束。数组的大小是10,所以有4个字节没用,不过没关系,结束符后的元素只是被忽略了。

变量 b 是指向字符的指针,因为 b 被赋值为 a,所以 ab 指向同一个字符('H')。变量 a 被定义为一个数组,它不能被改变,它总是指向数组的顶部地址。另一方面,b 是一个指针,它是可变的,然后可以编写像++b这样的语句,这意味着取 b 的值,将其加 1,并将其​​存储回 b 中。

如果指针为 NULL,则它不指向任何内容。另一方面,NULL 字符串的含义是空字符串,即指向某个位置的指针仅包含一个\0,它是一个长度为0(或“”)的字符串。所以要注意区别空指针和空字符的区别。

另一个问题是分配字符串的内存,有四种情况:

  • 字符串是只读的;
  • 字符串在静态内存区;
  • 字符串在栈中;
  • 字符串位于从堆区域分配的内存中。

只读字符串

C 程序中的字符串字面量用双引号括起来,写法如下:

char *s;
s = "Hello"

“Hello”是一个字符串字面量,存储在程序内存中,字符串字面量是只读的。在上面的程序中,s 指向字符串字面量所以,下面的程序是非法的:

*(s+1) = 'a';

这种操作的结果在 C 标准中没有定义,可能会发生不好的事情,例如段错误。

注意:字符串字面量的内存是在程序编译时分配的 可以使用 string 命令查看程序中定义的所有字符串字面量。

字符数组作为字符串

如果将字符串定义为数组,则它要么存储在静态内存区域中,要么存储在栈中。这取决于数组的类型,如果数组的类型是static,那么它被放置在静态内存区域中,这样分配的内存地址在程序的生命周期内是固定的,并且可以被修改或读取。

如果数组的类型是 auto,则将其放入栈,如果数组是在函数内部定义的,它的默认是 auto 类型,当函数退出并返回给调用者时,栈区将消失,栈上定义的数组是可写的。

static char a[] = {'H', 'e', 'l', 'l', 'o', '\0'};

void
print_strings (void) {
  char b[] = "Hello";

  a[1] = 'a'; /* Because the array is static, it's writable. */
  b[1] = 'a'; /* Because the array is auto, it's writable. */

  printf ("%s\n", a); /* Hallo */
  printf ("%s\n", b); /* Hallo */
}

数组“a”是在函数外部定义的,并且其范围是全局的,即使省略了“static”类,这些变量也会放在静态内存区域中。使用括号初始化,编译器会自动计算元素的数量(6个),然后在静态内存区域中分配六个字节的内存块,并将数据复制到该内存块中。

数组b在函数内部定义,所以它的类型是auto,编译器自动计算字符串中元素的数量,它有六个元素,因为还包括结束符。编译器在程序栈中分配六个字节的内存区域,并将数据复制到该内存区域。

ab 都是可写的。

像这样自动分配的内存由编译器产生的代码自动管理,您不需要为 ab 分配或释放内存。数组“a”的内存地址在程序的生命周期内保持不变。数组 b 在栈上创建,当函数返回时消失。

在堆上创建字符串

您还可以从堆区域获取、使用和释放内存。标准 C 库提供 malloc 来获取内存,使用 free 来释放内存。GLib 提供了函数 g_newg_free 来做同样的事情,并且支持一些额外的 Glib 功能。

g_new (struct_type, n_struct)

g_new 是一个为数组分配内存的宏。

  • struct_type 是数组元素的类型。
  • n_struct 是数组的大小。
  • 返回值是指向数组的指针,它的类型是指向 struct_type 的指针。

例如:

char *s;
s = g_new (char, 10);
/* s 指向 char 数组,数组大小为 10. */

struct tuple {int x, y;} *t;
t = g_new (struct tuple, 5);
/* t points an array of struct tuple. */
/* The size of the array is 5. */

g_free 释放内存:

void
g_free (gpointer mem);

如果 mem 为 NULL,则 g_free 什么都不做。gpointer 是一种通用指针,它与 void * 相同,该指针可以转换为任何指针类型。相反,任何指针类型也可以转换为 gpointer

g_free (s);
/* Frees the memory allocated to s. */

g_free (t);
/* Frees the memory allocated to t. */

如果指针不为 NULL,且指向的内存区域不是手动分配的,那么会造成段错误。

一些 Glib functions 也能分配内存,例如 g_strdup 分配一块内存,并将传入的字符串拷贝到分配的内存中:

char *s;
s = g_strdup ("Hello");
g_free (s);

字符串字面量“Hello”有 6 个字节,因为字符串末尾有 '\0'。g_strdup 从堆区获取 6 个字节大小的内存块,并将字符串复制到内存中。s 被分配了内存的顶部地址,g_free 将内存释放。

g_strdupGLib API Reference 中有描述,以下内容摘自参考资料。

当不再需要返回的字符串时,应该使用 g_free() 释放。

GLib 的 API 手册中对每个函数都说明了否需要释放返回的值。如果忘记释放分配的内存,它将保持被分配状态,多次要求系统分配,用完了却不释放,随着时间推移便会有大量的已经不再使用却还未归还给操作系统的内存,这种情况称为内存泄漏,解决此错误的唯一方法是关闭程序(并重新启动它),程序结束时,操作系统会自动将所有程序内存释放。

注意:一些 GLib 函数返回的字符串不允许调用者释放,例如下面的函数:

const char *
g_quark_to_string (GQuark quark);

此函数返回 const char* 类型的指针,限定符 const 表示返回值是不可变的,返回值所指向的字符不允许被改变或释放。

如果一个变量用 const 限定,则只能在初始化时赋值。

const int x = 10; /* initialization is OK. */

x = 20; /* This is illegal because x is qualified with const */

主页:教程介绍,上一节:第05节,下一节:第07节