Печать указателей на С - PullRequest
       8

Печать указателей на С

36 голосов
/ 13 октября 2008

Я пытался что-то понять с помощью указателей, поэтому я написал этот код:

#include <stdio.h>

int main(void)
{
    char s[] = "asd";
    char **p = &s;

    printf("The value of s is: %p\n", s);
    printf("The direction of s is: %p\n", &s);

    printf("The value of p is: %p\n", p);
    printf("The direction of p is: %p\n", &p);

    printf("The direction of s[0] is: %p\n", &s[0]);
    printf("The direction of s[1] is: %p\n", &s[1]);
    printf("The direction of s[2] is: %p\n", &s[2]);

    return 0;
}

При компиляции с помощью gcc я получаю следующие предупреждения:

$ gcc main.c -o main-bin -ansi -pedantic -Wall -lm
main.c: In function ‘main’:
main.c:6: warning: initialization from incompatible pointer type
main.c:9: warning: format ‘%p’ expects type ‘void *’, but argument 2 has type ‘char (*)[4]’
main.c:11: warning: format ‘%p’ expects type ‘void *’, but argument 2 has type ‘char **’
main.c:12: warning: format ‘%p’ expects type ‘void *’, but argument 2 has type ‘char ***’

(флаги для gcc - потому что я должен быть C89)

Почему несовместимые типы указателей? Разве имя массива не является указателем на его первый элемент? Так что, если s является указателем на 'a', &s должно быть char **, нет? И почему я получаю другие предупреждения? Нужно ли приводить указатели с помощью (void *), чтобы распечатать их?

И при запуске я получаю что-то вроде этого:

$ ./main-bin
The value of s is: 0xbfb7c860
The direction of s is: 0xbfb7c860
The value of p is: 0xbfb7c860
The direction of p is: 0xbfb7c85c
The direction of s[0] is: 0xbfb7c860
The direction of s[1] is: 0xbfb7c861
The direction of s[2] is: 0xbfb7c862

Как значение s и его направление (и, конечно, значение p) могут быть одинаковыми?

Ответы [ 8 ]

25 голосов
/ 13 октября 2008

"s" - это не "char *", это "char [4]". Итак, «& s» - это не «char **», а «указатель на массив из 4 символов». Ваш компилятор может трактовать «& s» так, как будто вы написали «& s [0]», что примерно то же самое, но это «char *».

Когда вы пишете "char ** p = & s;" Вы пытаетесь сказать «Я хочу, чтобы p был установлен на адрес вещи, которая в данный момент указывает на« asd ». Но в настоящее время нет ничего, что указывает на« asd ». Есть только массив содержит"asd";

char s[] = "asd";
char *p = &s[0];  // alternately you could use the shorthand char*p = s;
char **pp = &p;
15 голосов
/ 13 октября 2008

Да, ваш компилятор ожидает void *. Просто бросьте их в аннулирование *.

/* for instance... */
printf("The value of s is: %p\n", (void *) s);
printf("The direction of s is: %p\n", (void *) &s);
4 голосов
/ 13 октября 2008

Если вы передаете имя массива в качестве аргумента функции, оно обрабатывается так, как если бы вы передали адрес массива. Так & s и s - идентичные аргументы. Смотрите K & R 5.3. & s [0] - это то же самое, что и & s, поскольку он принимает адрес первого элемента массива, который совпадает с адресом самого массива.

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

  • void* p; говорит, что p - это адрес памяти, но я не знаю, что в памяти
  • char* s; говорит, что s является адресом памяти, а первый байт содержит символ
  • char** ps; говорит, что ps - это адрес памяти, и четыре байта там (для 32-битной системы) содержат указатель типа char *.

cf http://www.oberon2005.ru/paper/kr_c.pdf (электронная книга K & R)

3 голосов
/ 15 марта 2013

Это не указатель на символ char*, а указатель на массив из 4 символов: char* [4]. С g ++ он не компилируется:

main.cpp: в функции int int (int, char **): main.cpp: 126: error: невозможно преобразовать ‘char (*) [4]’ в ‘char **’ при инициализации

Более того, справочные страницы linux говорят :

р

Аргумент указателя void * печатается в шестнадцатеричном формате (как будто% # x или % # Лк). Это указатель на пустоту.

Вы можете изменить свой код на:

char* s = "asd";
char** p = &s;

printf("The value of s is: %p\n", s);
printf("The address of s is: %p\n", &s);

printf("The value of p is: %p\n", p);
printf("The address of p is: %p\n", &p);

printf("The address of s[0] is: %p\n", &s[0]);
printf("The address of s[1] is: %p\n", &s[1]);
printf("The address of s[2] is: %p\n", &s[2]);

результат:

Значение s: 0x403f00

Адрес s: 0x7fff2df9d588

Значение p: 0x7fff2df9d588

Адрес p: 0x7fff2df9d580

Адрес s [0]: 0x403f00

Адрес s [1]: 0x403f01

Адрес s [2]: 0x403f02

1 голос
/ 13 октября 2008

Обычно считается плохим стилем излишне приводить указатели на (void *). Здесь, однако, вам нужно привести (void *) к аргументам printf, потому что printf является переменным. Прототип не сообщает компилятору, в какой тип конвертировать указатели на сайте вызова.

1 голос
/ 13 октября 2008

Вы не можете изменить значение (т. Е. Адрес) статического массива. С технической точки зрения, lvalue массива является адресом его первого элемента. Отсюда s == &s. Это просто причуда языка.

1 голос
/ 13 октября 2008

строка изменения:

char s [] = "asd";

до:

char * s = "asd";

и все станет яснее

0 голосов
/ 13 октября 2008

Вы использовали:

char s[] = "asd";

Здесь s фактически указывает на байты "asd". Адрес s также указывает на это местоположение.

Если вы использовали:

char *s = "asd";

значение s и & будет другим, так как s фактически будет указателем на байты "asd".

Вы использовали:

char s[] = "asd";
char **p = &s;

Здесь s указывает на байты "asd". p - указатель на указатель на символы, и для него задан адрес символов. Другими словами, у вас слишком много косвенных указаний в p. Если вы использовали char * s = "asd", вы можете использовать это дополнительное косвенное указание.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...