Указатели на C, я так запутался

В настоящее время я слежу за этим pdf-файлом по указателям C. Приведенный ниже код был предназначен для демонстрации, но невероятно запутал меня. Во-первых, я понимаю указатели на массивы так, что они инициализируются в памяти кучи адресом памяти первого элемента в массиве, поэтому: ptr = &my_array[0]; /* and */ ptr = my_array; - это одно и то же. Моя первая проблема с этим кодом заключается в том, что он создал указатель, который не указывает на адрес памяти. Я думал, что инициализация указателя обычно включает &, а значение pA будет адресом памяти первого элемента массива, но когда он печатает указатель на экран, ВСЯ СТРОКА ПЕЧАТАЕТ. Почему? Вдобавок к этому, когда я помещаю еще один * в pA, он печатает первый символ массива. Почему? Вдобавок к этому он использует ++ для * pA и * pB, что для меня не имеет смысла, потому что вывод показывает, что * pA - это первый символ в массиве, а не адрес памяти.

Извините, что мне было сложно осмыслить эту концепцию. PDF просто плохо объясняет указатели или я что-то сильно не понимаю?

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

char strA[80] = "A string to be used for demonstration purposes";
char strB[80];

int main(int argc, char *argv[]){
    char *pA; /* a pointer to type character */
    char *pB; /* another pointer to type character */
    puts(strA); /* show string A */
    pA = strA; /* point pA at string A */
    puts(pA); /* show what pA is pointing to */
    pB = strB; /* point pB at string B */
    putchar('\n'); /* move down one line on the screen */
    printf("\n%c\n", *pA);
    while(*pA != '\0') /* line A (see text) */
    {
        *pB++ = *pA++; /* line B (see text) */
    }
    *pB = '\0'; /* line C (see text) */
    puts(strB); /* show strB on screen */
    return 0;
}

Вывод:

A string to be used for demonstration purposes
A string to be used for demonstration purposes


A
A string to be used for demonstration purposes

РЕДАКТИРОВАТЬ: Всем спасибо за помощь :), извините за вопрос новичка. Я прочитал «Программирование на C» Стивена Г. Кочана, и его удивительный раздел об указателях мне очень помог.


person Community    schedule 26.11.2020    source источник
comment
Вам может понравиться раздел 6 comp.lng.c faq ... а также другие разделы?   -  person pmg    schedule 26.11.2020
comment
@smolpatchy Вопрос слишком общий. Прочтите книгу на C для начинающих.   -  person Vlad from Moscow    schedule 26.11.2020
comment
Вскоре я отвечу на ваши конкретные вопросы с более подробной информацией, но да, чтение первых 4 глав «Язык программирования C» Кернигана и Ричи - это хорошая идея.   -  person Tim    schedule 26.11.2020


Ответы (4)


char strA[80] = "A string to be used for demonstration purposes";

Это делает пространство длиной 80 символов в глобальном разделе исполняемого файла - оно не выделяется динамически, поэтому его не нужно освобождать. Если strA используется в качестве указателя, он эквивалентен &strA[0], как и следовало ожидать.

Эти 80 символов заполняются строкой справа от равенства, включая завершающий символ в конце строки. Этот завершающий символ действительно важен, потому что именно так вы находите конец печатаемых данных в массиве.

puts(strA); /* show string A */

Когда вы puts(strA), эта функция печатает КАЖДЫЙ символ от адреса, переданного до этого завершающего символа - внутри этой функции есть цикл.

pA = strA; /* point pA at string A */
puts(pA); /* show what pA is pointing to */

Затем pA заполняется значением &strA[0], а затем, когда puts(pA); вызывается, функция получает тот же адрес, который получила в последний раз, поэтому она делает то же самое, что и в прошлый раз: она печатает КАЖДЫЙ символ из адреса, переданного до этого завершающего. персонаж.

printf("\n%c\n", *pA);
while(*pA != '\0') /* line A (see text) */

Символ * дает вам содержимое в том месте, где находится указатель, поэтому *strA дает то же самое, что и strA[0] - первый символ строки. И *pA то же самое, что и *strA, потому что они оба указывают на один и тот же адрес.

*pB++ = *pA++; /* line B (see text) */

Причина *pA++ сбивает вас с толку в том, что вы не знаете, * или ++ выполняется первым. Происходит следующее: pA++ увеличивает указатель на единицу и возвращает исходное значение, которое он имел. Затем * принимает это исходное значение указателя и передает его содержимое - символ, на который изначально указывал pA.

person Jerry Jeremiah    schedule 26.11.2020

В соответствии со стандартом C (6.3.2.1 L-значения, массивы и обозначения функций)

3 За исключением случаев, когда это операнд оператора sizeof или унарного оператора &, или строковый литерал, используемый для инициализации массива, выражение, имеющее тип "массив типа", преобразуется в выражение с типом "указатель на тип", который указывает на начальный элемент объекта массива и не является lvalue. Если объект массива имеет класс хранения регистров, поведение не определено.

Итак, эти два оператора присваивания

ptr = &my_array[0]; 

а также

ptr = my_array;

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

В C строки - это символьные массивы, которые содержат последовательности символов, оканчивающиеся нулевым завершающим символом '\0'.

Например, строковый литерал Hello хранится как массив символов с этой последовательностью символов.

{ 'H', 'e', 'l', 'l', 'o', '\0' }

Функция puts специально предназначена для вывода строк.

Об этом заявляется следующим образом

int puts(const char *s);

вызовы функции

puts(strA);
puts(pA)

эквивалентны, потому что снова указатель массива strA, используемый в качестве аргумента в первом вызове, неявно преобразуется в указатель на его первый элемент.

Разыменование указателя, как в этом вызове

 printf("\n%c\n", *pA);

вы получите объект, на который указывает указатель. Поскольку указатель pA указывает на первый символ массива strA, выводится этот символ.

person Vlad from Moscow    schedule 26.11.2020

В продолжение ответа Влада, и это здорово, я просто хотел добавить небольшой фрагмент кода, который действительно помог мне понять указатели. Я считаю, что действительно полезно делать прогнозы, а затем писать код и проводить наблюдения, пока я не пойму.

[ttucker@zim stackoverflow]$ cat ptr.c 
#include <stdio.h>

int main() {
    char *foo = "stuff";
    while (*foo != '\0') {
        printf("%lu: %c (%d), %lu\n", &foo, *foo, *foo, foo);
        foo++;
    }
    return 0;
}


[ttucker@zim stackoverflow]$ gcc -o ptr ptr.c


[ttucker@zim stackoverflow]$ ./ptr 
140730183740528: s (115), 94216543457284
140730183740528: t (116), 94216543457285
140730183740528: u (117), 94216543457286
140730183740528: f (102), 94216543457287
140730183740528: f (102), 94216543457288

# A second execution is included to show that the memory addresses change.

[ttucker@zim stackoverflow]$ ./ptr 
140723360085232: s (115), 94198997999620
140723360085232: t (116), 94198997999621
140723360085232: u (117), 94198997999622
140723360085232: f (102), 94198997999623
140723360085232: f (102), 94198997999624

person Tim    schedule 26.11.2020

Во-первых, я понимаю указатели на массивы так, что они инициализируются в памяти кучи адресом памяти первого элемента в массиве, поэтому: ptr = &my_array[0]; /* and */ ptr = my_array; - это одно и то же.

Единственный раз, когда в игру вступает куча, - это когда вы используете malloc, calloc или realloc или библиотечные функции, которые вызывают их за сценой. pA и pB занимают ту же память, что и переменные любого другого типа, объявленные в этой области.

но когда он выводит указатель на экран, ПЕЧАТАЕТСЯ ВСЯ СТРОКА.

Функция puts берет адрес первого символа в строке и проходит по строке, печатая каждый символ, пока не увидит терминатор строки, что-то вроде

void myputs( const char *str ) // str points to the first character in a 0-terminated string
{
  while ( *str )         // while we haven't seen the string terminator
    putchar( *str++ );   // print the current character and advance the pointer    
}

Вдобавок он использует ++ для * pA и * pB, что для меня не имеет смысла, потому что вывод показывает, что * pA - это первый символ в массиве, а не адрес памяти.

Линия

*pB++ = *pA++;

примерно эквивалентно написанию:

*pB = *pA;
pB = pB + 1;
pA = pA + 1;

Postfix ++ имеет более высокий приоритет, чем унарный *, поэтому *pA++ анализируется как *(pA++) - вы разыменовываете результат pA++. Результатом pA++ является текущее значение pA, а в качестве побочного эффекта pA увеличивается. Поскольку pA и pB являются указателями, добавление 1 продвигает их к следующему объекту в последовательности, не обязательно к следующему байту (хотя в данном конкретном случае следующий объект - это следующий байт).

person John Bode    schedule 27.11.2020