字符串文字和数组的地址

Address of a string literal and array

    int main(){
        char *str1="Hi", *str2 = "Bye";
        printf("%u,%u\n",&str1,str1);
        int arr[5]={1,2,3,4,5};
        printf("%u,%u",arr,&arr);
    }

这里发生了什么? str&str 给出不同的地址,arr&arr 给出相同的地址。

我的理解是 arr 指向第一个元素的地址,即 &arr[0]&arr 也会给出相同的地址,但它是整个 arr[5] 的地址。如果我们将 &arr 递增 1,那么它将指向 arr[4] 的下一个元素。但问题是为什么这个过程在字符串的情况下是不同的。请帮我想象一下这里的概念。

1.

char *str1="Hi";
printf("%u,%u\n",&str1,str1);

首先,您对 str1&str1 使用了错误的转换说明符 %u,这会调用未定义的行为。对于 str1 它应该是 %s 而对于 &str1 它应该是 %p:

char *str1="Hi";
printf("%p,%s\n",(void*) &str1, str1);

解释:

str1 是指向字符串文字 "Hi" 的第一个元素的地址的指针。 &str1是指针str1本身的地址。这就是下面数组版本的区别。


2.

int arr[5]={1,2,3,4,5};
printf("%u,%u",arr,&arr);

同样,此处的转换说明符错误。如果要打印 arr 的第一个元素,则应为 %d%i,因为 arrint 的数组而不是 unsigned int%p如果要打印第一个元素的地址:

int arr[5]={1,2,3,4,5};
printf("%p,%p",(void*) arr, (void*) &arr);

解释:

arr(在数组到指针衰减规则之后)衰减到指向 arr 的第一个元素的指针,而 &arr 实际上是指向 [ 的第一个元素的指针=27=]。他们实际上评估相同。


请注意,转换为 void* 是使代码符合 C 标准所必需的。

在 C 中,所有字符串文字实际上都存储为(只读)字符数组,包括空终止符。与任何其他数组一样,它们会衰减到指向其第一个元素的指针。

对于您代码中的 str1,编译器生成了一些类似于以下的代码:

// Compiler-generated array for the string
char global_array_for_Hi[] = { 'H', 'i', '[=10=]' };

int main(void)
{
    char *str1 = global_array_for_Hi;
    ...
}

您的指针变量 str1 指向该数组的第一个元素('H')。当您打印 str1 的值时,这就是您获得的值。

当您打印 &str1 的值时,您会得到变量 str1 本身的位置,您会得到一个指向 str1 类型 char ** 的指针。

在图形上有点像

+-------+     +------+     +-----+-----+------+
| &str1 | --> | str1 | --> | 'H' | 'i' | '[=36=]' |
+-------+     +------+     +-----+-----+------+

对于 arr 你有一个数组,它衰减到指向它的第一个元素的指针。当您使用 arr 时,它与 &arr[0] 相同(这是因为 arr[i] 正好等于 *(arr + i))。 arr(和&arr[0])的类型是int *

当你使用&arr时你得到一个指向整个数组的指针,它的类型是int (*)[5].

&arr[0]&arr 的位置相同,但它们的类型非常不同。


在相关说明中,printf 格式说明符 %u 用于打印 unsigned int 类型的值。要打印指针(更具体地说 void * 类型的值),您必须使用格式说明符 %p。格式说明符和参数类型不匹配导致 未定义的行为

array 不是指针。它是连续的内存块。这就是为什么 array && &array 具有相同地址

的原因

pointer 是一个保存引用的单独对象。所以 pointer - 给你指针持有的引用 &pointer 给你 pointer 本身的引用。由于指针是一个单独的对象,因此您有不同的地址

在此声明中

char *str1="Hi", *str2 = "Bye";

声明了两个局部变量str1str2,具有自动存储持续时间。

它们由具有静态存储持续时间的字符串文字的第一个字符的地址初始化。

所以str1的值就是字符串文字第一个字符的地址"Hi"。表达式&str1的值是局部变量str1本身的地址。

你可以这样想象

&str1 ---> str1 ---> "Hi"

数组是连续的内存范围。所以数组本身的地址和它的第一个元素的地址是相同的。数组占用内存范围的地址。

你可以这样想象

        | 1 | 2 | 3 | 4 | 5 |
        ^
        |        
&arr----
        ^
        |
arr-----

请注意,在极少数情况下,表达式中使用的数组指示符会转换为指向其第一个元素的指针。所以使用 din 调用 printf 表达式 arr 等同于 &arr[0].

关于您的评论

Vlad I've a doubt regard string constant, Why can't we modify this char *s="HI" but we can modify this char *s[]="HI" I know about second case it is simple array but can you please clear why can't i modify string constant

然后根据 C 标准(6.4.5 字符串文字)

7 It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.

注意这个声明

char *s[]="HI";

无效。声明了一个指针数组。所以要初始化它你必须写

char * s[] = { "HI" };

并且您可以通过为数组的元素分配其他字符串文字来更改它们。也就是说,您可以更改指针本身,而不是指针指向的字符串文字。

在C语言中,像"Hi, Guys"这样的常量字符串存储在共享内存中。 考虑以下示例:

str = "Hi, there";

上面代码中的字符串是连续存储的。变量 str 指向 字符串的第一个字符,So character 'H' 这里。 因此,str 给出存储在内存某处的第一个字符的地址。 &str 给出变量 str 本身的地址。

数组中的大小写与上面不同。 数组是变量(当然是常量变量),它包含数组的第一个元素(即 &arr[0])的地址。当您执行 &arr 时,它将与 &arr[0] 相同。 &arr 实际上是整个数组的地址,与数组第一个元素的地址相同。

注意:打印&arr + 5&arr[0] +5,你可能会得到一些光。