Путание указателей в C

У меня есть больше чем одно сомнение, поэтому терпите меня. Кто-то может сказать мне, почему этот код перестал работать?

#include<stdio.h>
void main(int argc,char **argv) /*assume program called with arguments aaa bbb ccc*/
{
    char **list={"aaa","bbb","ccc"};

    printf("%s",argv[1]);/*prints aaa*/
    printf("%s",list[1]); /*fails*/ 
}

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

#include<stdio.h>
void main()
{
char **list={"aaa","bbb","ccc"};
char *ptr;
ptr=list;
printf("%s",ptr);/*this prints the first string aaa*/
    /* My second question is how do i increment the value
       of ptr so that it points to the second string bbb*/
}

Между чем различие char *list[] и char **list и в том, какие ситуации оба идеальны, чтобы использоваться? Еще одной вещью, смущающей меня, является argv специальное предложение? когда я передаю char **list к другой функции, принимающей это, позволил бы мне получить доступ к содержанию путем, я мог с argv, это также перестало работать.

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

7
задан Eby John 21 January 2010 в 11:46
поделиться

5 ответов

Вы должны использовать CHAR * Список [] = {"AAA", "BBB", "CCC" }; вместо Char ** Список = {«AAA», «BBB», «CCC»}; . Вы используете CHAR * Список [] = {...}; - объявить массив указателей, но вы используете Char ** , чтобы пройти указатель на один или несколько указателей на функцию.

  • T * x [] = массив указателей
  • T ** x = указатель на указатель

P.S. Отвечая на Эеон: есть только одно использование, что я могу подумать о создании указателя на указатель (в качестве фактической заявленной переменной, не в качестве параметра функции или временной, созданной Оператором & ): Ручка . Короче говоря, ручка - это указатель на указатель, где Handl; E принадлежит пользователю, но указатель, который он указывает, чтобы быть изменены по мере необходимости по ОС или библиотеке.

Ручки широко использовались по всей старой Mac OS. Поскольку Mac OS был разработан без технологии виртуальной памяти, единственным способом сохранить кучу от быстрое получение фрагментации - это использовать ручки практически во всех ассигновании памяти. Это позвольте ОС перемещать память по мере необходимости для компактного кучи и открывать более крупные, смежные блоки свободной памяти.

Истина в том, что эта стратегия в лучшем виде просто «высосала меньше». Существует огромный список недостатков:

  • Общая ошибка состояла, когда программисты разыгрывают ручку на указатель, и используют этот указатель для нескольких вызовов функций. Если какая-либо из этих функций вызывает перемещенную память, произошла вероятность того, что указатель станет недействительным, и разыграть, что он будет поврежден памятью и, возможно, сбой программы. Это коварная ошибка, поскольку разыгрывание плохого указателя не приведет к ошибке шины или ошибки сегментации, поскольку сама память была все еще существующей и доступной; Это просто больше не использовалось объектом, который вы используете.
  • По этой причине компилятор должен был быть дополнительным осторожным, а некоторые общие оптимизации ликвидации под воздействием не могут быть приняты (общая подзванение, являющаяся деременями ручки к указателю).
  • Итак, для обеспечения надлежащего выполнения практически все доступ к ручкам требуют двух косвенных доступов, а не один с простым старым указателем. Это может повредить производительность.
  • Каждый API, предоставленный ОС или любой библиотекой, должен был указать, может ли оно может «переместить память». Если вы назвали одну из этих функций, все ваши указатели, полученные через ручки, теперь были недействительными. Не было никакого способа, чтобы IDE сделать это для вас или проверить вас, поскольку вызов перемещения памяти и указатель, который стал недействительным , может даже не быть в одном и том же исходном файле.
  • Производительность становится недетерминированной, потому что вы никогда не знаете, когда ОС приостановит паузу для компактной памяти (которая включала в себя много из MEMCPY () .
  • Многопоточность становится сложным, потому что один поток может перемещать память, в то время как другой выполняется или заблокирован, недействительный его указатели. Помните, что ручки должны использоваться практически для всех распределений памяти, чтобы сохранить от фрагментации кучи, поэтому потоки все еще могут потребоваться доступом к памяти через ручку, даже если они используют None Mac OS API.
  • Были функционные вызовы для блокировки и разблокировки указателей, указанных на ручки, однако слишком много блокировки больно производительность и фрагменты куча.

Возможно, еще несколько, что я забыл. Помните, что все эти недостатки были еще более привлекательны, чем использование только указателей и быстро фрагментируя кучу, особенно на первых Mac, которые имели только 128k ОЗУ. Это также дает некоторое представление о том, почему Apple была идеально рад отюжем все это и пойти в BSD, тогда у них была возможность, когда вся их линейка продукта имела единицы управления памятью.

12
ответ дан 6 December 2019 в 10:50
поделиться

Лучший источник для изучения сложностей C - это программирование книги эксперта по Peter Van der Linden ( http://www.amazon.co.uk/expert-programming-peter-van-linden/dp / 0131774298 ).

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

1
ответ дан 6 December 2019 в 10:50
поделиться

запустите приложение в режиме отладки, F5 в основном представляет собой сочетание клавиш в visual studio. Установите точку останова в этом заявлении. Используйте F10 для перехода к следующей инструкции. Как только отладчик выполнит следующую инструкцию, вы увидите создаваемый объект.

-121--4648519-

Я бы инкапсулировал создание класса, который вам нужен на заводе.

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

    class YourClassFactory {

        private $_language;
        private $_basename = 'yourclass';

        public YourClassFactory($language) {
            $this->_language = $language;
        }

        public function getYourClass() {
            return $this->_basename . '_' . $this->_language;
        }    
    } 

и затем, когда вы должны использовать его:

$yourClass = $yourClassFactoryInstance->getYourClass();
$yourClass::myFunctionName();
-121--975507-

char * * x указывает на массив указателей символов, однако это может быть не так, как компилятор хранит {«aaa», «bbb», «ccc»} в памяти. char * x [] приведет к созданию правильного кода независимо от того, как компилятор хранит массив указателей.

2
ответ дан 6 December 2019 в 10:50
поделиться

»... предполагается, что у него есть что делать С указателем на то указатель вещи, Что я не понимаю понятно ».

Как работает на массиве указателей на указатели?

0
ответ дан 6 December 2019 в 10:50
поделиться

Прежде всего, давайте разберемся с мелочами. main возвращает int , а не void. Если в документации вашего компилятора специально не указано, что он поддерживает void main () , используйте int main (void) или int main (int argc, char ** argv) .

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

Массив - это блок памяти размером для хранения N элементов типа T; указатель - это блок памяти размером для хранения адреса одного значения типа T. Вы не можете присвоить новое значение объекту массива; т.е. следующее недопустимо:

int a[10], b[10];
a = b;

Обратите внимание, что строковый литерал (например, «aaa») также является выражением массива; тип представляет собой массив из N элементов char (const char в C ++), где N - длина строки плюс завершающий 0.Строковые литералы имеют статический размер; они выделяются при запуске программы и существуют до выхода из программы. Они также не могут быть записаны (попытка изменить содержимое строкового литерала приводит к неопределенному поведению). Например, тип выражения «aaa» - это 4-элементный массив char со статическим размером. Как и другие выражения массива, строковые литералы в большинстве случаев переходят от типов массива к типам указателей. Когда вы пишете что-то вроде

char *p = "aaa";

, выражение массива «aaa» распадается с char [4] на char * , и его значение является адресом первой «a» множество; этот адрес затем копируется в p .

Если литерал используется для инициализации массива char, однако:

char a[] = "aaa";

, то тип не преобразуется; литерал по-прежнему обрабатывается как массив, и содержимое массива копируется в a a неявно имеет размер, чтобы содержать содержимое строки плюс 0 терминатор). Результат примерно эквивалентен записи

char a[4];
strcpy(a, "aaa");

Когда выражение массива типа T a [N] является операндом оператора sizeof , результатом является размер всего массива в байтах: N * sizeof (T). Когда это операнд оператора адресации ( & ), результатом является указатель на весь массив, а не указатель на первый элемент (на практике это то же самое значение , но типы разные):

Declaration: T a[N];  

 Expression   Type        "Decays" to  Value
 ----------   ----        -----------  ------
          a   T [N]       T *          address of a[0]
         &a   T (*)[N]                 address of a
   sizeof a   size_t                   number of bytes in a
                                        (N * sizeof(T))
       a[i]   T                        value of a[i]
      &a[i]   T *                      address of a[i]
sizeof a[i]  size_t                    number of bytes in a[i] (sizeof (T))

Обратите внимание, что выражение массива a распадается на тип T * или указатель на T.Это тот же тип, что и выражение & a [0] . Оба этих выражения дают адрес первого элемента в массиве. Выражение & a имеет тип T (*) [N] или указатель на массив из N элементов T, и оно дает адрес самого массива, а не первого элемента. . Поскольку адрес массива совпадает с адресом первого элемента массива, a , & a и & a [0] все дают то же значение , но не все выражения одинакового типа . Это будет иметь значение при попытке сопоставить определения функций с вызовами функций. Если вы хотите передать массив в качестве параметра функции, например

int a[10];
...
foo(a);

, тогда соответствующее определение функции должно быть

void foo(int *p) { ... }

То, что получает foo , является указателем на int, а не массив int. Обратите внимание, что вы можете называть его либо foo (a) , либо foo (& a [0]) (или даже foo (& v) , где v - простая переменная типа int, хотя if foo ожидает массив, который вызовет проблемы). Обратите внимание, что в контексте объявления параметра функции int a [] совпадает с int * a , но только истинно в этом контексте. Откровенно говоря, я думаю, что форма int a [] вызывает множество путаниц в отношении указателей, массивов и функций, и ее использование не рекомендуется.

Если вы хотите передать указатель на массив функции, такой как

int a[10];
foo(&a);

, тогда соответствующее определение функции должно быть

void foo(int (*p)[10]) {...}

, и если вы хотите сослаться на конкретный элемент, вы должны разыменовать указатель перед , применив нижний индекс:

for (i = 0; i < 10; i++)
  (*p)[i] = i * i;

Теперь давайте бросим гаечный ключ в работу и добавим второе измерение в массив:

Declaration: T a[M][N];

  Expression   Type        "Decays" to  Value
  ----------   ----        -----------  ------
           a   T [M][N]    T (*)[N]     address of a[0]
          &a   T (*)[M][N]              address of a
    sizeof a   size_t                   number of bytes in a (M * N * sizeof(T))
        a[i]   T [N]       T *          address of a[i][0]
       &a[i]   T (*)[N]                 address of a[i]
 sizeof a[i]   size_t                   number of bytes in a[i] (N * sizeof(T))
     a[i][j]   T                        value of a[i][j]
    &a[i][j]   T *                      address of a[i][j]

Обратите внимание, что в этом случае,оба a и a [i] являются выражениями массива, поэтому их соответствующие типы массивов в большинстве случаев будут преобразованы в типы указателей; a будет преобразован из типа «M-element array of N-element array of T» в «указатель на N-элементный массив T», а a [i] будет преобразован из "N-элементного массива T" в "указатель на T". И снова, a , & a , a [0] , & a [0] и & a [0] [ 0] все будут давать одинаковые значения (адрес начала массива), но не будут все одинаковыми типами . Если вы хотите передать 2-мерный массив функции, например:

int a[10][20];
foo(a);

, тогда соответствующее определение функции должно быть

void foo(int (*p)[20]) {...}

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

for (i = 0; i < 10; i++)
  for (j = 0; j < 20; j++)
    p[i][j] = i * j;

. В этом случае вам не нужно явно разыменовать p , потому что выражение p [i] неявно уважает его ( p [i] == * (p + i) ).

Теперь давайте посмотрим на выражения указателей:

Declaration: T *p;
Expression   Type    Value
----------   ----    ------
         p   T *     address of another object of type T
        *p   T       value of another object of type T
        &p   T **    address of the pointer 
  sizeof p   size_t  number of bytes in pointer (depends on type and platform,
                      anywhere between 4 and 8 on common desktop architectures)
 sizeof *p   size_t  number of bytes in T
 sizeof &p   size_t  number of bytes in pointer to pointer (again, depends
                      on type and platform)

Все довольно просто. Тип указателя содержит адрес другого объекта типа T; разыменование указателя ( * p ) дает значение по этому адресу, а взятие адреса указателя ( & p ) дает местоположение объекта указателя (указатель на указатель) .Применение sizeof к значению указателя даст количество байтов в указателе, а не количество байтов, на которые указывает указатель.

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

Вы хотите создать массив указателей на char и инициализировать его тремя строковыми литералами, поэтому вы должны объявить его как

char *list[] = {"aaa", "bbb", "ccc"};

Размер массива list имеет неявный размер хранить 3 элемента типа char * . Хотя строковые литералы «aaa», «bbb» и «ccc» появляются в инициализаторе, они не используются для инициализации массива char; следовательно, они распадаются от выражений типа char [4] до типа char * . Каждое из этих значений указателя копируется в элементы списка .

Когда вы передаете список функции, такой как

foo(list);

, тип списка распадается с «4-элементный массив указателя на char» ( char * [4] ) на «указатель на указатель на char» ( char ** ), поэтому функция приема должна иметь определение

void foo(char **p) {...}

. Поскольку индексирование определяется в терминах арифметики указателя, вы можете использовать оператор индекса в указатель , как если бы это был массив char * :

for (i = 0; i < 3; i++)
  printf("%s\n", p[i]);

Кстати, вот как main получает argv как указатель на указатель на char ( char ** ), а не как массив указателя на char.Помните, что с точки зрения объявления параметра функции, a [] идентично * a , поэтому char * argv [] идентично char ** argv .

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

Если вы хотите динамически распределять свой список во время выполнения (т. Е. Заранее не знаете, сколько строк находится в вашем списке), вы должны объявить list как указатель на указатель на char, а затем вызвать malloc , чтобы фактически выделить для него память:

char **list;
size_t number_of_strings;
...
list = malloc(number_of_strings * sizeof *list);
list[0] = "aaa";
list[1] = "bbb";
list[2] = "ccc";
...

Поскольку это присваивания, а не инициализации, буквальные выражения превращаются в указатели на char, поэтому мы копируем адреса из «aaa», «bbb» и т. Д. В записи в списке . В этом случае список - это , а не тип массива; это просто указатель на кусок памяти, выделенный где-то еще (в данном случае из кучи malloc). Опять же, поскольку индексирование массива определяется в терминах арифметики указателя, вы можете применить оператор индексации к значению указателя , как если бы это был массив. Тип выражения list [i] - char * . Не стоит беспокоиться о неявных преобразованиях; если вы передадите его в функцию как

foo(list)

, тогда определение функции будет

void foo(char **list) {...}

, и вы поставите индекс list , как если бы это был массив.

Пссст ... он готов?

Да, я думаю, он закончил.

3
ответ дан 6 December 2019 в 10:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: