&array的sizeof返回什么?

35

针对这个问题:为什么在C语言中数组的地址等于它的值?

#include <stdio.h>
#define N 10    
char str2[N]={"Hello"};
int main(){
    printf("sizeof(str2): %d bytes\n", sizeof(str2));
    printf("sizeof(&str2): %d bytes\n", sizeof(&str2));
    return 0;
}

输出:

sizeof(str2): 10 bytes
sizeof(&str2): 4 bytes

我知道str2单独是数组str2中第一个元素的地址。当str2作为sizeof的参数时,它返回整个数组str2的大小。此外,&str2也是数组str2中第一个元素的地址,但类型不同(char (*)[N] == 指向数组的指针)。但是当&str2作为sizeof的参数时,它会如何表现?

7
“我知道str2本身是数组str2中第一个元素的地址” - 其实不是。在大多数情况下,它会被转换为指向其第一个元素的指针,但sizeof是其中的例外之一。 - Daniel Fischer
@DanielFischer,为什么 printf("%p %p",str2, str2+1); 打印第一个和第二个元素的地址? - user1980750
3
因为它不是例外情况之一。除非它是 sizeof_Alignof 或取地址运算符 & 的操作数,或在字符串字面值的情况下作为 char[] 的初始化器,否则数组类型的表达式将被转换为指向该数组第一个元素的指针。因此,在大多数情况下,数组的名称会被评估为指向其第一个元素的指针,但它确实是不同的东西(即数组的名称)。 - Daniel Fischer
1
@DanielFischer: _Alignof 不是其中的例外,因为 _Alignof 只能应用于带括号的类型名,而不能应用于表达式。这是N1570 草案中的错误,在发布的 ISO C11 标准中已经得到了纠正。(至于 why _Alignof 不能应用于表达式,那就是另外一个问题了。) - Keith Thompson
@KeithThompson 同时,我知道这个。我想是你告诉我的,但也可能是其他人。不管怎样,还是谢谢你,即使这是重复的感谢。 - Daniel Fischer
@DanielFischer:你的第二条评论帮了很多忙,这就是我喜欢数学家所说的一句话的原因。 - VimNing
4个回答

118

&strstr之间的区别,当str声明为char str[10]时?

阅读sizeof运算符:

6.5.3.4 sizeof运算符,1125:
当对数组类型应用sizeof运算符时,结果是数组中字节的总数。

因此,根据您的声明,sizeof(str)会给出完整的数组大小,即10个字节(因为N定义为10,而char大小为1字节)。
而在表达式sizeof(&str)中,&str是一个数组的地址,该地址在您的系统上的大小为4字节。(在某些系统上,如64位系统中,地址大小可能为8字节)。

此外,&str也是arr str中第一个元素的地址吗?

不是,就值而言,&strstr是相同的,但从语义上讲,它们是不同的。其中一个是10个字符数组的地址,而另一个是字符的地址。

您可以在自己的示例中看到它们之间的一个区别(@ouah在答案中解释了这一点)。

  • str的类型是char[10]
  • &str的类型是char(*)[10]

其次:观察下面的图表将帮助您观察到另一个区别。

for declaration: 
#define N 10
char str2[N] = {"Hello"};

str2 Array in memory is something like:
----------------------------------------

str
+----+----+----+----+----+----+----+----+----+----++----+
|'H' |'e' |'l' |'l' |'o' |'\0'|'\0'|'\0'|'\0'|'\0'|| '@'|
+----+----+----+----+----+----+----+----+----+----++----+
 201   202  203 204  205   206  207  208  209  210   211
▲ ▲     ▲                                             ▲
| |     |                                             |
|(str2) (str2 + 1)                                    | 
|                                                     |
|-----------------------------------------------------|
|201                                                  | 
|                                                     |
|                                                     |
(&str2) = 201                           (&str2 + 1) = 211


* assuming str address start from 201
* str[N] is 10 char long 201-210, partially initialized
* at uninitialized position, str2[i] = '\0'
* location 211 is unallocated, having garbage value,
  access to this location is illegal-Undefined Behavior

对于上面的图表,你可以编写如下代码:

#include <stdio.h>
#define N 10    
int main(){
   char str2[N]={"Hello"};
  
   printf("\n %p, %p\n",str2, str2+1);
   printf("\n %p, %p\n",(&str2), (&str2+1));
}  

输出:

 0xbf67e142, 0xbf67e143

 0xbf67e142, 0xbf67e14c

一个codepad链接

请注意,在第一行,输出地址与原地址相差一个字节,但在第二行中,由于这是指向数组的指针(如上图所示),因此差异为10个字节。

根据指针算术规则,将1添加到指针变量时,它开始指向其自身类型的下一个元素。这就是10字节差异的原因,因为&str2是指向数组的地址。

第三个差异:

通过使用*str2,您可以访问第一个元素。而*(&str2)不会给您第一个元素,而是会给出第一个元素的地址。

这里有一个例子:

#include <stdio.h>
#define N 10    
int main(){
   char str2[N]={"Hello"};
   printf("\n%p %c, %p %c\n",str2, *(str2), *(&str2), **(&str2));
}  

输出:

0xbf587046 H, 0xbf587046 H

Codepad链接

输出中

str2 gives  0xbf587046 
*(str2)     H 
*(&str2)    0xbf587046 
**(&str2)   H 

这意味着 *(&str2) == str2 并且value是地址。因此,*(str2) = **(&str2),值为 H

编辑:上面我展示了&strstr之间的区别,其中str是类型为char[10] 的数组。

char *strchar str[]之间的区别以及它们在内存中的存储方式

假设我们有以下两个声明:

char *str1 = "hello";   
char str2[] = "hello";  
在上述声明中,str1 是指向常量字符串字面值的 char 指针(通过保存在 "hello" 字符串中第一个字符 h 的地址进行指向)。

C 中的字符串是 char[N](数组)类型,这就是为什么 sizeof("hello") 返回 6 的原因,因为 "hello" 字符串是一个 6 个字符长的数组(包括 \0 空字符,用于字符串终止,hello 的类型是 char[6])。

在内存中,"hello" 字符串的存储方式如下:

 str1         23   24   25   26   27   28
+----+      +----+----+----+----+----+----+
| 23 |      | h  | e  |  l | l  | o  | \0 |    
+----+      +----+----+----+----+----+----+
   +-----------▲

here the address of the hello string is the first address = 23.  
str1: is a pointer capable of storing an address. 
"hello" consists of 6 chars

char* str1 = "hello";基本上将字符串“hello”的地址存储到指针变量str1中,正如我在上面的图中所示。

注意:如果需要,可以将str1更改为指向其他字符串。但是无法修改字符串“hello”。例如,以下代码是有效的:

 char* str1 = "hello";  // str1 points to hello  str1-->"hello"
 str1 = "world";  //Now, str1 points to world  str1-->"world"
现在str1指向另一个常量字符串“world”。
 str1         93   94   95   96   97   98 
+----+      +----+----+----+----+----+----+
| 93 |      | w  | o  |  r | l  | d  | \0 |    
+----+      +----+----+----+----+----+----+
   +-----------▲

here address of world string is first address = 93.  
str1: value change to point string world. 

需要注意的是:str1 指向常量字符串,因此您不能通过访问/索引内存位置来修改字符串,例如 str1[i] = 'A'; 这样做将是非法的,因为您正在写入只读内存,这在运行时的行为是未定义的(虽然没有编译错误,因为从语法上讲它是正确的)。

同样,由于 str1 是指针,在同一机器上使用 sizeof(str1) 将会返回 4。

以下是我的代码及其运行结果:

#include <stdio.h>
int main(){
   char* str1="Hello";
   printf("\nstr1: %s, address: %p, sizeof(str1): %u", str1, str1, sizeof(str1));
   str1 = "world";
   printf("\nstr1: %s, address: %p, sizeof(str1): %u", str1, str1, sizeof(str1));
   return 1;
}  

输出:

str1: Hello, address: 0x80485e8, sizeof(str1): 4
str1: world, address: 0x8048619, sizeof(str1): 4

Codepad链接

为了分配新的字符串,我只需分配一个新字符串的地址。但是我不能调用strcpy(),因为它会尝试写入只读内存位置,这是不合法的。

在第二个声明中char str2[] = "hello";str2 []是以\0结尾的字符数组(或字符串),而不是指针。请注意,因为在此声明中未给定大小默认大小,我们可以看到常量字符串“hello”的大小为6。 str2的类型是char [6]

当我们执行char str2[] = "hello";时,将创建一个char数组,并将hello字符串复制到该数组中。str2不仅仅是一个指针,而是一个存储完整字符串的数组。

从概念上讲,就像

       str2:
       103  104  105  106  107  108
      +----+----+----+----+----+----+
      | h  | e  |  l | l  | o  | \0 |    
      +----+----+----+----+----+----+

最近在你的代码中,你不能使用str2[] = "world";或者str2 = "world",这将会导致编译时错误。

示例代码:

#include<stdio.h>
int main(){
 char str2[] = "hello";
 str2[] = "world";
 str2 = "world"; 
 return 1; 
}

编译错误:

In function 'main':
Line 4: error: expected expression before ']' token
Line 5: error: incompatible types in assignment

Codescape链接

当这个数组str2不是常量时,我们可以修改它的内容,例如执行str2[2] ='A'是完全有效的。我们也可以调用strcpy来改变内容(地址空间不会改变)。

       strcpy(str2, "world");

       str2:
       103  104  105  106  107  108
      +----+----+----+----+----+----+
      | w  | o  |  r | l  | d  | \0 |    
      +----+----+----+----+----+----+

      Note that when "world" is copied into a same memory space, the addresses of both "world" and "hello"
      string are the same.  

代码示例:

#include<stdio.h>
int main(){
 char str2[] = "hello";
 printf("\nstr2: %s, address: %p, sizeof(str2): %u", str2, str2, sizeof(str2));
 str2[2] = 'A';
 printf("\nstr2: %s, address: %p, sizeof(str2): %u", str2, str2, sizeof(str2));
 strcpy(str2, "world");
 printf("\nstr2: %s, address: %p, sizeof(str2): %u", str2, str2, sizeof(str2));
 return 1; 
}

输出:

str2: hello, address: 0xbf58d056, sizeof(str2): 6
str2: heAlo, address: 0xbf58d056, sizeof(str2): 6
str2: world, address: 0xbf58d056, sizeof(str2): 6

Codepad链接

注意:在同一地址空间中,字符串值是不同的。sizeof(str2) = 6,从早期的答案可以完全理解这是字节数组的大小。

要阅读关于二维数组的类似描述,请阅读:char* str[]和char str[][]之间的区别以及两者在内存中如何存储?


21

&str2是一个指针。因此,您只能看到您的平台上指针的大小。


9
并不一定所有指针类型的大小都相同,虽然大多数实现都是如此。 - Keith Thompson

11

str2 是类型为 char [10] 的数组(即,数组长度为 10char 数组)

&str2 是类型为 char (*)[10] 的指针(即,指向长度为 10char 数组的指针)

因此,sizeof(&str2) 返回的是指针类型 char (*)[10] 对象的大小


1
如果您只是使用变量arr(如定义的char arr[10]),它总是会衰减为一个指向第一个元素的常量指针char *const arr。而&arr的结果是一个指针char (*)[10],其中包括arr的大小,即10

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接