2016-02-12 3 views
3

Есть множество вопросов о указателях здесь, на SO, и бесчисленных ресурсах в Интернете, но я до сих пор не смог это понять.Тип указателя?

This answer котировки A Tutorial on Pointers and Arrays in C: Chapter 3 - Pointers and Strings:

int puts(const char *s); 

На данный момент, игнорирующие Уст. Параметр, переданный в puts(), является указателем, то есть значением указателя (поскольку все параметры в C передаются по значению), а значение указателя - это адрес, на который он указывает, или, просто, адрес. Таким образом, когда мы пишем puts(strA);, как мы видели, мы передаем адрес strA[0].

Я этого не понимаю.

  • Почему puts() нужен указатель на строку постоянной? puts() не изменяет и не возвращает свой аргумент, просто записывает его в stdout, а затем строка отбрасывается.

  • Игнорирование почему, как же это прототип puts() «s, который в явной форме принимает указатель на строковую константу, принимает строковый литерал, а не указатель на один? То есть, почему puts("hello world"); работает, когда прототип puts() указывает, что puts() нуждается в чем-то более похожем на char hello[] = "hello world"; puts(&hello);?

  • Если вы даете, например, printf()указатель в строку постоянной, которая, по-видимому, что он хочет, GCC будет жаловаться и ваша программа будет сегментации, потому что:

    error: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[6]’ 
    

Но давая printf(), строковая константа, а не указатель на строку, отлично работает.

This Programmers.SE question's answers сделать для меня большой смысл.

Отправляя ответы на этот вопрос, указатели - это просто цифры, которые представляют собой положение в памяти. Номера для адресов памяти являются неподписанными ints, а C записывается на (родной) C и сборке, поэтому указатели просто определены в архитектуре uint s.

Но это не так, так как компилятор очень ясно в своих ошибках о том, как int, int * и int ** являются не то же самое. Это путь, который в конечном итоге указывает на что-то в памяти.

Почему функции, которые нуждаются в указателе принять то, что является не указателя, и отклонить указатель?


Я знаю, что «строковая константа» на самом деле представляет собой массив символов, но я пытаюсь упростить здесь.

+1

Я прочитал ваш вопрос. Это немного сбивает меня с толку. Не могли бы вы просто сказать мне, что вы ожидаете? –

+0

@JafferWilson там, я надеюсь, что я сделал это яснее – cat

+2

Возможно, вы захотите прочитать, что происходит с массивом при передаче функции (в C). – alk

ответ

2

Почему puts() нужен указатель на строку постоянной? puts() не изменяет и не возвращает свой аргумент, просто записывает его в stdout, а затем строка отбрасывается.

puts получает указатель на первого символа в строке; он будет «ходить» вниз по этой строке, пока не увидит 0-терминатор. Наивный реализация будет выглядеть примерно так:

void puts(const char *ptr) 
{ 
    while (*ptr)  // loop until we see a 0-valued byte 
    putchar(*ptr++); // write the current character, advance the pointer 
         // to point to the next character in the string. 
    putchar('\n'); 
} 

Игнорирование почему, как это, что прототип puts() «s, который в явной форме принимает указатель на строковую константу, принимает строковый литерал, а не указатель на один? То есть, почему puts("hello world"); работает, когда прототип puts() указывает, что puts() нуждается в чем-то более похожем на char hello[] = "hello world"; puts(&hello);?

исключения случаев, когда это операнд оператора sizeof или унарный &, или является буквальным существом строки используется для инициализации другого массива в объявлении, выражения типа «Н-элемент массив T» будет преобразовать («распад») в выражение типа «указатель на T», а значение выражения будет адресом первого элемента массива.

Строковые литералы хранятся в виде массивов char (const char in C++); Таким образом, строковый литерал "hello world" является выражением типа «12-элементный массив char». Когда вы вызываете puts("hello world");, строковый литерал не является операндом операторов sizeof или унарных &, поэтому тип выражения преобразуется в «указатель на char», а значение выражения - это адрес первого символа в строка.

Если вы даете, например, printf() указатель на строковую константу, которая, по-видимому, что он хочет, GCC будет жаловаться и ваша программа будет сегментации, потому что:

error: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[6]’

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

char hello[] = "hello world"; 

Как выше, выражение "hello world" имеет тип 12-элементный массив char; однако, поскольку он используется для инициализации другого массива char в объявлении, он не преобразуется в выражение указателя; вместо этого содержимое строкового литерала копируется в массив hello.

Точно так же, если вы звоните printf следующим образом:

printf("%s", &hello); 

то выражение hello является не преобразуется в указатель на char; вместо этого тип выражения &hello является «указателем на 12-элементный массив char», или char (*)[12]. Поскольку спецификатор %s преобразования ожидает char *, вы должны просто передать выражение массива в

printf("%s", hello); 

и строковых литералов, просто использовать буквальный:

printf("%s", "hello world"); 

Уходя отвечает на этот вопрос, в указатели являются просто цифрами, которые представляют собой позицию в памяти. Номера для адресов памяти являются неподписанными ints, а C записывается на (родной) C и сборке, поэтому указатели просто определены в архитектуре uint s.

Но это не так, поскольку в его ошибках компилятор очень прост в том, что int, int * и int ** - это не то же самое. Это путь, который в конечном итоге указывает на что-то в памяти.

C является (более или менее) строго типизированным языком; типы вопрос. Даже при том, что int, int * и int **может занять такое же количество пространства в памяти , семантически они очень разные вещи, и (обычно) не взаимозаменяемы. Указатель на int является отдельным типом от указателя до float, который является отдельным типом от указателя на массив char и т. Д. Это имеет значение для вещей, таких как арифметика указателей; когда вы пишете

T *p = some_address(); 
p++; 

Выражение p++ продвигает p, чтобы указать на следующий объект типа T. Если sizeof (T) равно 1, то p++ продвигает один байт; если sizeof (T) равно 4, то p++ продвигает 4 байта (предполагая архитектуру с байтовым адресом, над которой большинство из нас работает).


1. Или нет. Нет никакой гарантии, что указатели на разные типы имеют одинаковый размер или представление как друг друга, а также не гарантируют, что они представляют собой целые числа без знака; на сегментированной архитектуре у них может быть более сложная страница: смещение.

1

int ** r = 90; r - двойной указатель, и вы назначаете 90 указателю. Когда вы разыщите, он попытается разыменовать адрес 0x90.

2

Несколько вопросов, но, надеюсь, я смогу проиллюстрировать, как работают указатели на указатели.

Причина, по которой puts нужна указатель, заключается в том, что у C не существует встроенного типа для строки. Строка - это всего лишь одна цепочка из char. Следовательно, puts нуждается в указателе на первый из символов.

Строковый литерал, «грамотно деформируется» на указатель. Это фантастический компилятор, означающий, что строковый литерал фактически является строкой символов и представлен указателем на первый из char.

Вам нужен указатель на указатель на тип, например, если вы хотите, чтобы «вернуть» массив из функции, например, так:

bool magic_super_function(int frob, int niz, char** imageptr /* pointer to pointer */) 
{ 
    char* img = malloc(frob * niz * IMAGE_DEPTH); 
    if (NULL == ptr) { 
     return false; 
    } 

    *imageptr = img; 

    return true; 
} 

Иногда пример (даже надуманный) можно проиллюстрировать точка. Вы назвали бы эту функцию следующим образом:

char* img; /* pointer to char */ 

if (true == magic_super_function(12, 8, &img /* pointer to pointer (to char)*/ )) { 
    /* Here img is valid and allocated */ 
    /* Do something with img */ 
} else { 
    /* img has no valid value here. Do not use it. */ 
    /* Something failed */ 
} 
+0

'ставит' не делает. 'puts' возвращает int, представляющий количество символов, которые он написал. – cat

+0

Я спросил о двух конкретных функциях 'printf' и' puts', оба имеют тип 'int', который является длиной строки, которую они написали. Я не понимаю, зачем им нужен указатель. – cat

+1

@cat, что 'puts' делает, или не делает, не так ли? :-) Вышеприведенный пример предназначен для иллюстрации того, как вы можете использовать указатель на указатель. –

3

puts() не нужен указатель на строку, он нужен указатель (*) к персонажу (char). Бывает, что в C указатель на символ (char *) может быть ассимилирован в строку (массив символов), при условии, что конец строки является нулевым символом \0.

+0

Означает ли это, что 'char s []' и 'char s' не являются разными типами, только один из них представляет собой набор указателей на' char'? – cat

+2

Нет, 'char s' - это символ, а не массив символов. – mouviciel

1

Почему puts() нужен указатель на константу строки?

определяется таким образом, чтобы он мог использовать фактический параметр вместо его копирования и повторного использования. Потому что это повышает производительность. Кроме того, он принимает указатель const, поэтому он не может изменить содержимое, указанное указателем. Это позвоните по телефону.

Как же это прототип puts() «s, который явно принимает указатель на строковая константа, принимает строковый литерал, а не указатель на один?

Когда вы передаете строковый литерал, сначала строковый литерал хранится в памяти только для чтения, а затем передается указатель на эту память. Таким образом, вы можете позвонить puts() с любым литералом, как puts("abcd"), puts("xyz"). Это будет работать.

error: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[6]’

Вот ваш фактически передавая указатель на массив из 6 char ов а не char *. Поэтому компилятор будет жаловаться на эту ошибку.

2

int отличается от int* тем, как он будет использоваться в коде.Вы можете рассчитывать получить доступ к ячейке памяти, на которую указывает int* и найти целочисленное значение. Это называется «сильная типизация», и язык делает это так, что существуют строгие правила использования ваших переменных. Таким образом, хотя значения int и int* могут быть одного размера, int не может использоваться в качестве указателя. Точно так же int** является указателем на указатель, поэтому его нужно будет разыменовать дважды, чтобы найти фактическое целочисленное значение, на которое оно ссылается.

В примере puts(const char*) определение функции говорит о том, что функция ожидает, что ячейка памяти (указатель) будет иметь набор с нулевым завершением значений char. При выполнении операции puts будет разыменовывать местоположение, которое вы ему даете, и распечатайте найденные там символы. Часть const сообщает вам, что она не будет изменять значения так, чтобы было безопасно отправить массив const из char. Когда вы отправляете литеральную строку, например puts("hello"), компилятор превращает это в указатель на «привет» для вас в качестве удобства, поэтому указатель по-прежнему отправляется (а не копия строки).

Что касается вашего вопроса о printf, обратите внимание, что char* и char*[6] отличаются. Первый указывает на указатель на строку с нулевым завершением, где второй - указатель на набор из шести значений char, которые могут не заканчиваться нулями. Компилятор жалуется, потому что если puts(&hello) попытался обработать входной параметр как строку с завершающим нулевым символом, это не остановится после того, как длина массива и будет доступ к памяти, которой она не должна.

4

Выражение "hello world" имеет тип char[12].

В большинстве контекстов, использование массива преобразуется в указатель на его первый элемент: в случае "hello world" он преобразуется в указатель на 'h', типа char*.

При использовании puts("Hello world") массив преобразуется в char*.

Обратите внимание, что преобразование из массива определенного размера, теряет информацию о размере.

char array[42]; 
printf("size of array is %d\n", (int)sizeof array); 
printf("size of pointer is %d\n", (int)sizeof &array[0]); 
+0

Ah, поэтому * * конверсия. Это имеет большее значение, спасибо. – cat

+0

Да, есть неявное преобразование. Вам ничего не нужно делать, это всегда выполняется. Наиболее заметным исключением является то, что массив используется как операнд в операнде 'sizeof'.Другие исключения - это когда массив используется как операнд оператора '&' (address of) и используется в качестве строкового литерала для инициализации объекта массива. – pmg

Смежные вопросы