Адрес строкового литерала и массива - PullRequest
1 голос
/ 13 апреля 2020
    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]. Но проблема в том, почему этот процесс отличается в случае строки. Пожалуйста, помогите мне визуализировать концепцию здесь.

Ответы [ 5 ]

6 голосов
/ 13 апреля 2020

В C все строковые литералы действительно хранятся в виде массивов (только для чтения) символов, включая нулевой терминатор. Как и любой другой массив, они распадаются на указатель на свой первый элемент.

Для str1 в вашем коде компилятор сгенерировал некоторый код, подобный следующему:

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

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

Ваша переменная указателя str1 указывает на первый элемент ('H') этого массива. Когда вы печатаете значение str1, это значение вы получаете.

Когда вы печатаете значение &str1, вы получаете местоположение самой переменной str1, вы получаете указатель на str1 типа char **.

Несколько графически это можно увидеть как

+-------+     +------+     +-----+-----+------+
| &str1 | --> | str1 | --> | 'H' | 'i' | '\0' |
+-------+     +------+     +-----+-----+------+

Для arr у вас есть массив, он распадается на указатель на свой первый элемент , Когда вы используете arr, это то же самое, что и &arr[0] (это происходит из-за того, что arr[i] точно равно *(arr + i)). Тип arr&arr[0]) - int *.

Когда вы используете &arr, вы получаете указатель на весь массив, и его тип равен int (*)[5].

Местоположение одинаково для &arr[0] и &arr, но их типы очень разные.


В связанной заметке спецификатор формата printf %u предназначен для печати значений типа unsigned int. Для печати указателей (точнее, значений типа void *) необходимо использовать спецификатор формата %p. Несоответствие спецификатора формата и типа аргумента приводит к неопределенному поведению .

1 голос
/ 13 апреля 2020

В C постоянная строка типа «Привет, ребята» сохраняется в общей памяти. Рассмотрим следующий пример:

str = "Hi, there";

Строка в приведенном выше коде хранится непрерывно. Переменная str указывает на первый символ строки, поэтому здесь символ 'H'. Итак, str дает адрес первого символа, который хранится где-то в памяти. &str дает адрес самой переменной str.

Случай в массиве отличается от описанного выше. Массив - это переменная (конечно, переменная const), которая содержит адрес первого элемента (то есть & arr [0]) массива. И когда вы сделаете &arr, оно будет таким же, как &arr[0]. & arr на самом деле является адресом всего массива, который совпадает с адресом первого элемента массива.

Примечание: выведите &arr + 5 и &arr[0] +5, у вас может быть немного света.

1 голос
/ 13 апреля 2020

В этом объявлении

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

объявлены две локальные переменные str1 и str2 с автоматическим c сроком хранения.

Они инициализируются по адресам первой символы строковых литералов, которые имеют длительность хранения c.

Таким образом, значение str1 является адресом первого символа строкового литерала "Hi". Значением выражения &str1 является адрес самой локальной переменной str1.

Это можно представить следующим образом

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

Массивы являются смежными экстентами памяти. Таким образом, адрес самого массива и адрес его первого элемента совпадают. Это адрес объема памяти, занятого массивом.

Вы можете представить это следующим образом

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

Обратите внимание, что указатели массива, используемые в выражениях с редкими исключениями, преобразуются указатели на их первые элементы. Так что используйте din для вызова printf выражение arr эквивалентно &arr[0].

Относительно вашего комментария

Влад У меня есть сомнения относительно строковой константы. Почему можно не изменяем ли мы этот char * s = "HI", но мы можем изменить этот char * s [] = "HI" Я знаю, что во втором случае это простой массив, но не могли бы вы пояснить, почему я не могу изменить строковую константу * 1031? *

затем в соответствии со стандартом C (6.4.5 Строковые литералы)

7 Не определено, различаются ли эти массивы при условии, что их элементы имеют соответствующие значения. Если программа пытается изменить такой массив, поведение не определено.

Обратите внимание на это объявление

char *s[]="HI";

недопустимо. Там объявлен массив указателей. Таким образом, чтобы инициализировать его, вы должны написать

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

И вы можете изменить элементы массива, назначив им другие строковые литералы. То есть вы можете изменять сами указатели, а не строковые литералы, на которые указывают указатели.

1 голос
/ 13 апреля 2020

array не является указателем. Это непрерывный кусок памяти. Причина, по которой array && &array имеют одинаковый адрес

pointer, заключается в том, что отдельный объект содержит ссылку. Таким образом, pointer - дает ссылку, удерживаемую указателем, а &pointer - ссылку на сам pointer. Так как указатель является отдельным объектом, у вас разные адреса

1 голос
/ 13 апреля 2020

1.

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

Сначала вы используете неправильный спецификатор преобразования, %u для обоих, str1 и &str1, что вызывает неопределенное поведение. Для 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);

Опять здесь неправильные спецификаторы преобразования. Это должно быть %d или %i, если вы хотите напечатать первый элемент arr, поскольку arr является массивом int, а не unsigned int или %p, если вы хотите напечатать адрес первый элемент:

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

Объяснение:

arr (после правила затухания массива в указатель) затухает до указателя на первый элемент arr, тогда как &arr фактически указатель на первый элемент arr. Они на самом деле оцениваются одинаково.


Обратите внимание, приведение к void* необходимо для того, чтобы код C соответствовал стандарту.

...