C字符串混乱

lam*_*mas 19 c string

我现在正在学习C并且对字符数组 - 字符串感到困惑.

char name[15]="Fortran";
Run Code Online (Sandbox Code Playgroud)

没问题 - 它的数组可以容纳(最多?)15个字符

char name[]="Fortran";
Run Code Online (Sandbox Code Playgroud)

C计算我的字符数,所以我没有 - 整洁!

char* name;
Run Code Online (Sandbox Code Playgroud)

好的.现在怎么办?我所知道的是,这可以容纳后来分配的大量字符(例如:通过用户输入),但是

  • 为什么他们称之为char指针?我知道指针作为变量的引用
  • 这是"借口"吗?这是否找到除char*之外的任何其他用途?
  • 这究竟是什么?它是指针吗?你如何正确使用它?

提前谢谢,喇嘛

t0m*_*13b 33

我认为这可以用这种方式解释,因为一张图片胜过千言万语......

我们将从开始char name[] = "Fortran",这是一个字符数组,长度在编译时已知,确切地说是7,对吧?错误!它是8,因为'\ 0'是一个空终止字符,所有字符串都必须有.

char name[] = "Fortran";
+======+     +-+-+-+-+-+-+-+--+
|0x1234|     |F|o|r|t|r|a|n|\0|
+======+     +-+-+-+-+-+-+-+--+ 

在链接时,编译器和链接器为符号name提供了0x1234的内存地址.使用下标运算符,name[1]例如,编译器知道如何计算内存中偏移处的字符,0x1234 + 1 = 0x1235,并且它确实是"o".这很简单,此外,使用ANSI C标准,char数据类型的大小为1个字节,这可以解释运行时如何获取此语义的值name[cnt++],假设cnt是一个integer并且值为3,例如,运行时自动加1,从零开始计数,偏移量为't'.到目前为止这很简单.

如果name[12]被执行会怎么样?好吧,代码会崩溃,或者你会得到垃圾,因为数组的边界是从索引/偏移0(0x1234)到8(0x123B).之后的任何东西都不属于name变量,这将被称为缓冲区溢出!

name内存中的地址是0x1234,如示例中所示,如果您这样做:

printf("The address of name is %p\n", &name);

Output would be:
The address of name is 0x00001234

为了简洁和保持示例,内存地址是32位,因此您可以看到额外的0.很公平?对,让我们继续吧.

现在指向... char *name是指向char....的类型的指针

编辑: 我们将它初始化为NULL如图所示感谢Dan指出小错误...

char *name = (char*)NULL;
+======+     +======+ 
|0x5678| ->  |0x0000|    ->    NULL
+======+     +======+ 

在编译/链接时,name它没有指向任何东西,但是具有符号的编译/链接时间地址name(0x5678),实际上它是NULL,指针地址name是未知的,因此是0x0000.

现在,请记住,这是至关重要的,符号的地址在编译/链接时是已知的,但在处理任何类型的指针时指针地址是未知的

假设我们这样做:

name = (char *)malloc((20 * sizeof(char)) + 1);
strcpy(name, "Fortran");

我们调用malloc为20个字节分配一个内存块,不,它不是21,我加上1的大小的原因是'\ 0'nul终止字符.假设在运行时,给出的地址是0x9876,

char *name;
+======+     +======+          +-+-+-+-+-+-+-+--+
|0x5678| ->  |0x9876|    ->    |F|o|r|t|r|a|n|\0|
+======+     +======+          +-+-+-+-+-+-+-+--+

所以当你这样做时:

printf("The address of name is %p\n", name);
printf("The address of name is %p\n", &name);

Output would be:
The address of name is 0x00005678
The address of name is 0x00009876

现在,这就是" 阵列和指针相同的幻觉在这里发挥作用 "

当我们这样做时:

char ch = name[1];

运行时会发生什么:

  1. name查找符号的地址
  2. 获取该符号的内存地址,即0x5678.
  3. 在该地址处,包含另一个地址,指向存储器的指针地址并获取它,即0x9876
  4. 根据下标值1获取偏移量并将其添加到指针地址,即0x9877,以检索该存储器地址的值,即"o"并分配给ch.

上面的内容对于理解这种区别至关重要,数组和指针之间的区别在于运行时如何使用指针获取数据,还有一个额外的取向间接.

请记住,T类型 的数组总是会衰减为 T类型的第一个元素的指针.

当我们这样做时:

char ch = *(name + 5);
  1. name查找符号的地址
  2. 获取该符号的内存地址,即0x5678.
  3. 在该地址处,包含另一个地址,指向存储器的指针地址并获取它,即0x9876
  4. 获取基于值5的偏移量并将其添加到指针地址,即0x987A以检索该存储器地址处的值,即"r"并分配给ch.

顺便说一下,你也可以对字符数组这样做...

进一步,通过在阵列即环境中使用标运算符char name[] = "...";name[subscript_value]真的一样*(名称+ subscript_value).即

name[3] is the same as *(name + 3)

既然表达*(name + subscript_value)可交换的,那就相反了,

*(subscript_value + name) is the same as *(name + subscript_value)

因此,这解释了为什么在上面的一个答案中你可以这样写(尽管如此,即使它是非常合理的,也不推荐这种做法!)

3[name]

好的,我如何获得指针的值?这就是*用于的,假设指针的name指针存储器地址为0x9878,再次参考上面的例子,这就是它的实现方式:

char ch = *name;

这意味着,获取0x9878的内存地址所指向ch的值,现在将具有值'r'.这称为解除引用.我们只是取消引用一个name指针来获取值并将其赋值给ch.

此外,编译器知道a sizeof(char)为1,因此您可以像这样执行指针递增/递减操作

*name++;
*name--;

指针会自动向上/向下逐步上升/下降.

当我们这样做时,假设指针内存地址为0x9878:

char ch = *name++;

*name的值是什么,地址是什么,答案是,*name现在将包含't'并将其分配给ch,并且指针存储器地址是0x9879.

在这里你必须要小心,与前面关于内存边界的内容相同的原则和精神(参见上文中"如果名称[12]被执行时会发生什么")结果将是相同的,即代码崩溃和烧伤!

现在,如果我们解除分配的内存块会发生什么指向name通过调用C函数freename作为参数,即free(name):

+======+     +======+ 
|0x5678| ->  |0x0000|    ->    NULL
+======+     +======+ 

是的,内存块被释放并传回运行时环境,供另一个即将执行的代码执行使用malloc.

现在,这就是分段错误的常用符号发挥作用的地方,因为name它没有指向任何东西,当我们取消引用它时会发生什么,即

char ch = *name;

是的,代码将崩溃并以"分段故障"刻录,这在Unix/Linux下很常见.在Windows下,将出现一个对话框,其中包含"不可恢复的错误"或"应用程序发生错误,您是否希望将报告发送给Microsoft?"....如果指针尚未显示malloc,则试图取消引用它,保证崩溃和燃烧.

另外:记住这一点,因为每一个malloc都有相应的free,如果没有相应的free,你有一个内存泄漏,其中分配了内存但没有释放.

而且你有它,就是指针的工作方式以及数组与指针的不同之处,如果你正在阅读一本说它们相同的教科书,那就撕下那个页面并撕掉它!:)

我希望这有助于你理解指针.

  • 未初始化并不意味着初始化为NULL. (2认同)