2017-01-10 3 views
15

Экспериментируя с методами пошагового массив строк в C, я разработал следующую небольшую программу:У меня три петли над массивом элементов (char *) в C. Почему третий сбой?

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


typedef char* string; 

int main() { 
    char *family1[4] = {"father", "mother", "son", NULL}; 
    string family2[4] = {"father", "mother", "son", NULL}; 

    /* Loop #1: Using a simple pointer to step through "family1". */ 
    for (char **p = family1; *p != NULL; p++) { 
    printf("%s\n", *p); 
    } 
    putchar('\n'); 

    /* Loop #2: Using the typedef for clarity and stepping through 
    * family2. */ 
    for (string *s = family2; *s != NULL; s++) { 
    printf("%s\n", *s); 
    } 
    putchar('\n'); 

    /* Loop #3: Again, we use the pointer, but with a unique increment 
    * step in our for loop. This fails to work. Why? */ 
    for (string s = family2[0]; s != NULL; s = *(&s + 1)) { 
    printf("%s\n", s); 
    } 
} 

Мой конкретный вопрос включает в себя отказ Loop # 3. При прохождении через отладчик Loops # 1 и # 2 завершаются успешно, но последний цикл выходит из строя по неизвестной причине. Я бы не спросил об этом здесь, кроме факта, что показывает мне, что у меня есть некоторые критические недоразумения относительно оператора «&».

Мой вопрос (и текущее понимание) заключается в следующем:

family2 является символом массива из-указателя к. Таким образом, когда s установлено на family2[0], у нас есть (char*), указывающий на «отец». Поэтому принятие &s должно дать нам эквивалент family2, указывая на первый элемент family2 после ожидаемого распада указателя. Почему же тогда *(&s + 1) указывает на следующий элемент, как и ожидалось?

Большое спасибо,
lifecrisis


EDIT - обновление и извлеченные уроки:

Следующий список представляет собой краткое изложение всех соответствующих фактов и интерпретаций, которые объясняют, почему третий цикл не работает, как первые два.

  1. s представляет собой отдельную переменную, содержащую копию значения (указатель к полукокса) от переменной family2[0]. I.e., эти два эквивалентных значения расположены в SEPARATE местах в памяти.
  2. family2[0] до family2[3] являются смежными элементами памяти, а s не имеет присутствия в этом пространстве, хотя он содержит то же значение, которое хранится в family2[0] в начале нашего цикла.
  3. Эти первые два факта означают, что &s и &family2[0] НЕ равны. Таким образом, добавление одного к &s вернет указатель на неизвестные/неопределенные данные, тогда как добавление одного к &family2[0] даст вам &family2[1], если требуется.
  4. Кроме того, шаг обновления в третьем цикле for фактически не приводит к тому, что s будет выполняться вперед в памяти на каждой итерации. Это связано с тем, что &s является постоянным во всех итерациях нашего цикла. Это является причиной наблюдаемого бесконечного цикла.

Благодаря КАЖДОМУ на их помощь!
lifecrisis

+1

Что такое 'string'? C не имеет строкового типа. – Olaf

+2

Обратите внимание на typedef в верхней части блока кода! Благодаря! – lifecrisis

+6

А, да! Таким образом, применяется другое предупреждение: ** Никогда еще ** 'typedef' указатель! Он запутывает код, спам пространства имен и делает код, отличный от кода, сложным, если не невозможным. – Olaf

ответ

19

Когда вы s = *(&s + 1) переменная s является локальной переменной в неявной области, которая содержит только петлю. Когда вы делаете &s, вы получаете адрес этой локальной переменной, которая не связана ни с одним из массивов.

Отличие от предыдущего цикла заключается в том, что s является указателем на первый элемент массива.


Чтобы объяснить это немного больше «графически», что у вас есть в последнем цикле что-то вроде

 
+----+  +---+  +------------+ 
| &s | ---> | s | ---> | family2[0] | 
+----+  +---+  +------------+ 

То есть, &s указывает на s и s указывает на family2[0].

Когда вы делаете &s + 1 вы эффективно что-то вроде

 
+------------+ 
| family2[0] | 
+------------+ 
^ 
| 
+---+---- 
| s | ... 
+---+---- 
^ ^
| | 
&s &s + 1 
+0

Я соглашусь, что 's' доступен только локально внутри цикла. Однако, не должно '& s' быть, фактически,' (char **) 'указывая на первый элемент' family2', который сам является '(char *)'? – lifecrisis

+3

@lifecrisis Нет, '& s' является указателем, указывающим на' s', больше ничего. –

+0

Интересно, почему этот совершенно правильный ответ был опущен. –

9

Изображения помогают много:

  +----------+ 
      | "father" |          
      +----------+   +----------+  +-------+  NULL 
    /-----------→1000   | "mother" |  | "son" |  ↑ 
+-----+   ↑    +----------+  +-------+  | 
| s | ?   |     2000   2500   | 
+-----+   |     ↑    ↑   | 
6000 6008 +----------------+----------------+--------------+--------------+ 
      | family2[0] | family2[1] | family2[2] | family2[3] | 
      +----------------+----------------+--------------+--------------+ 
        5000    5008   5016   5024 

        ( &s refers to 6000 ) 
        (&s+1 refers to 6008 but) 
        ( *(&s+1) invokes UB ) 

Адреса выбраны в качестве случайных чисел для простоты


Вещь вот что, хотя бот h s и family2[0] указывают на тот же базовый адрес строкового литерала "father", указатели не связаны друг с другом и имеют свою собственную память, в которой они хранятся. *(&s+1) != family2[1].

Вы попадаете в UB, когда вы делаете *(&s + 1), потому что &s + 1 - это место памяти, в которое вы не должны вмешиваться, т. Е. Оно не принадлежит ни одному из объектов, которые вы создали. Вы никогда не знаете, что там хранится => Undefined Behavior.

Thanks @2501 для указания нескольких ошибок!

+0

В последней части вашего ответа вы имеете в виду '& s + 1! = Family2 [1]'? Оба утверждения верны, но мне просто интересно, что более важно заметить в этом контексте ... – lifecrisis

+1

Это правда, но типы разные. 's + 1! = family2 [1]' является сопоставлением двух 'char *', а '& s + 1! = family2 [1]' является сравнением символов 'char **' и 'char *', которые на самом деле не имеет смысла. –

+2

Непонятно, какой пример вы представляете здесь. Несмотря на это, это неправильно. & s + 1 не указывает ни на что. Это даже не действительный объект. – 2501

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


typedef char* string; 

int main() { 
char *family1[4] = { "father", "mother", "son", NULL }; 
string family2[4] = { "father", "mother", "son", NULL }; 

/* Loop #1: Using a simple pointer to step through "family1". */ 
for (char **p = family1; *p != NULL; p++) { 
    printf("%s\n", *p); 
} 
putchar('\n'); 

/* Loop #2: Using the typedef for clarity and stepping through 
* family2. */ 
for (string *s = family2; *s != NULL; s++) { 
    printf("%s\n", *s); 
} 
putchar('\n'); 

/* Loop #3: Again, we use the pointer, but with a unique increment 
* step in our for loop. This fails to work. Why? */ 
/*for (string s = family2[0]; s != NULL; s = *(&s + 1)) { 
    printf("%s\n", s); 
} 
*/ 
for (int j = 0; j < 3; j++) 
{ 
    printf("%d ",family2[j]); 
    printf("%d\n", strlen(family2[j])); 
} 
printf("\n"); 
int i = 0; 
for (string s = family2[i]; i != 3; s = (s + strlen(family2[i]) + 2),i++) { 
    printf("%d ",s); 
    printf("%s\n", s); 
} 

system("pause"); 

}

это пример пересмотренный из вашего кода, если вы запустите его, вы увидите изменение адреса точки и family2, то вы поймете, Взаимоотношение петля № 3.

+0

's = (s + strlen (family2 [i]) + 2)' перемещает 's' за пределы строки –