C中的字符串如何在内存中看起来像?
我想弄清楚2d char数组在内存中的样子。 例如:C中的字符串如何在内存中看起来像?
char c[][5]={"xa","ccc","bb","j","a","d"};
printf("TEST: %u %u %u %u \n\n",c[0],*c[0],c[0]+1,*(c[0]+1));
输出:
TEST:3214246874 120 3214246875 97个
C [0] = *(C + 0)是字符串 “XA”,并等于3214246874,所以我猜c [0]是char数组“xa”的地址。 当我把一个*到c [0]时,我得到了120,这是ascii中的'x'。
所以我认为c数组中的第一个空格是char x的地址。 之后,我尝试了与c [0] +1相同的内容,并打印了下一个地址,然后我将*和我得到了97,这是ascii中的'a'。
所以我假设数组c是这样的:
c[0] c[1]
------------------------------------------------------------------
| pointer to x | pointer to a ||| pointer to c | pointer to c | etc ...
----------------------------------------------------------------------
,但我在网上搜索,和我没有找到我的假设任何证据。
您混为一谈的“串”一词的两种意义,因为它是在C.
使用的大多数正确,C字符串是空终止阵列的char
。你已经声明了一个数组char
,并用空字符串序列初始化它。将其描述为“字符串数组”是完全合理的。
然而,阵列与指针完全不一样。你阵列中的元素是其他阵列,每个阵列(在你的情况下)是五个长度为chars
。这就是“串”这个词的另一种意义.C数组有点滑,如果评估一个数组类型的(子)表达式,它将评估一个指向第一个数组元素的指针。在字符串的情况下,这样的指针的类型为char *
,所以它通常指的是将字符串指向字符串本身。然而,这是一种口语主义,如果你不认识到这两种相关含义之间的差异,你就会陷入麻烦。
打破您的示例代码:
char c[][5]={"xa","ccc","bb","j","a","d"}; printf("TEST: %u %u %u %u \n\n",c[0],*c[0],c[0]+1,*(c[0]+1));
表达
c[0]
指定五个char
阵列。在函数调用表达式的上下文中进行求值时,它将成为指向数组的第一个元素的指针。该值的类型为char *
,它不是对应的printf
字段描述符%u
的正确类型。未定义的行为结果。您可以通过将参数转换为void *
并将字段描述符更改为%p
来更正此问题。鉴于
c[0]
计算结果为指针到第一构件阵列的第一char
,它遵循表达式*c[0]
评估为被指向的char
。该值再次不能匹配相应的字段描述符,该描述符应该是%c
- 然后您应该预期打印出“x”。或者,您可以施加值:(unsigned int)*c[0]
。在这种情况下,您会希望打印'x'的数字代码;这很可能是120.事实上,实际上打印的值是程序未定义行为的具体表现形式的一个无关紧要的特征。再次给出该
c[0]
计算结果为指针到第一构件阵列的第一char
,接下去c[0] + 1
是指针另外,产生一个指向数组中的第二char
。与c[0]
一样,这与格式不匹配。想必将清楚由这一点上,
*(c[0] + 1)
计算结果为所述第二char
(在索引1)的阵列c[0]
。该表达严格等同于c[0][1]
。这又与格式不匹配。
所以我假设数组c是这样的[...]
都能跟得上。阵列看起来像这样:
| c[0] | c[1] | c[2] | c[3] | c[4] | c[5] |
x a \0 \0 \0 c c c \0 \0 b b \0 \0 \0 j \0 \0 \0 \0 a \0 \0 \0 \0 d \0 \0 \0 \0
谢谢你!所以在实际内存中的c [0]是'x',但是在C中它将被评估为子数组的第一个元素的地址? – Daniel2708
不,@ Daniel2708,c [0]'在内存中的表示不是(仅)'x'。 'c [0]'是一个由5个char组成的数组,它们是用特定的'char'序列'xa \ 0 \ 0 \ 0'初始化的。这五个'char'值*一起*是它在内存中的表示。但是,当C评估一个包含或由'c [0]'组成的表达式,该表达式指定一个数组时,该数组将被指向其第一个元素的指针替换以评估表达式。 –
@JohnBollinger *与特定的字符序列xa \ 0 \ 0 \ 0 * - 是不是最后两个'\ 0'实际上是“不在乎”? –
c
内部应该是这样:
c[0] c[1] c[2] c[3]
| | | |
[0] [1] [2] [3] [4] [0] [1] [2] [3] [4] [0] [1] [2] [3] [4]
'x' 'a' '\0' '\0' '\0' 'c' 'c' 'c' '\0' '\0' 'b' 'b' '\0' '\0' '\0' ...
即这是一个长长的字符序列。它不存储任何指针或地址。
编译器知道每个部分的大小,所以当你编写例如它知道从偏移量2 * 5 + 1
= 11
(从c
的开头)获取它。
这条线:
char c[][5] = {"xa", "ccc", "bb", "j", "a", "d"};
可以更明确地写成:
char c[6][5] = {"xa\0\0\0", "ccc\0\0", "bb\0\0\0", "j\0\0\0\0", "a\0\0\0\0", "d\0\0\0\0"};
c
的是6
元素,其中每个元素是char[5]
类型的阵列。每个“子数组”需要5
字节(char
总是需要一个字节),并且它们彼此相邻放置。因此,c
阵列占用的总内存空间为30
字节。
注意:指针数组和数组是不同的动物!一旦定义,你几乎以相同的方式使用它们,但它们在内存中的存储方式不同。指针的
-
阵列:
char *c[]={"xa","ccc","bb","j","a","d"};
这定义的6个指针的数组。这些指针中的每一个都指向将被存储在其他地方的字符串。一个典型的表示将是:
c -> address_of_x, address_of_c, address_of_ ... (array of pointers) 'x', 'a', '\0', 'c', 'c', 'c', '\0', 'b'... (arrays of chars) - - -
整个事情将使用(在32位体系结构):6 * 4 + 3 + 4 + 3 + 2 + 2 + 2 = 40个字节
-
2D阵列:
char c[][5]={"xa","ccc","bb","j","a","d"};
这定义的6行5列的每(恰好30个字节)的二维数组:
'x', 'a', '\0', ?, ?, 'c', 'c', 'c', '\0', ?, 'b' ...
(字节s指出?是不关心,它们可能被初始化或不依赖于实现和构建选项)。
但是,无论定义你使用,c[1][2]
将成为第二个字符串的第三个字符,并*(c[0] + 1)
(其定义一样c[0][1]
是)是第一个字符串的第二个字符,那就是:x
。
它将只是一个大小为5 * 6的连续内存区域,其中包含字符。没有指针将被存储在那里。 –
使用'%u'打印指针调用UB。 –
Eugene Sh。是正确的,但只要看一下调试器中的内存,或者打印出来,以便为证据提供支持。 – LordWilmore