У меня три петли над массивом элементов (char *) в C. Почему третий сбой?
При экспериментировании с методами для перехода по массиву строк в 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
- это массив-указатель на char. Таким образом, когда s
установлено на family2[0]
, мы имеем a (char*)
, указывающий на "отец". Поэтому принятие &s
должно дать нам эквивалент family2
, указывая на первый элемент family2
после ожидаемого распада указателя. Почему же тогда нет,
*(&s + 1)
указывает на следующий элемент, как ожидалось?
Большое спасибо,
lifecrisis
EDIT - обновление и извлеченные уроки:
Следующий список представляет собой резюме всех соответствующих фактов и интерпретаций, которые объясняют, почему третий цикл не работает, как первые два.
-
s
- это отдельная переменная, содержащая копию значения (указатель-to- char) из переменной family2[0]
. I.e., эти два эквивалентных значения расположены в SEPARATE местах в памяти.
-
family2[0]
до family2[3]
являются смежными элементами памяти, а s
не имеет присутствия в этом пространстве, хотя оно содержит то же значение, которое хранится в family2[0]
в начале нашего цикла.
- Эти первые два факта означают, что
&s
и &family2[0]
НЕ равны. Таким образом, добавление одного в &s
вернет указатель на неизвестные /undefined данные, тогда как добавление одного в &family2[0]
даст вам &family2[1]
, если требуется.
- Кроме того, шаг обновления в третьем цикле for фактически не приводит к тому, что s будет выполняться вперед в памяти на каждой итерации. Это связано с тем, что
&s
является постоянным во всех итерациях нашего цикла. Это является причиной наблюдаемого бесконечного цикла.
Благодаря КАЖДОМУ за их помощь!
lifecrisis
Ответы
Ответ 1
Когда вы выполняете s = *(&s + 1)
, переменная s
является локальной переменной в неявной области, содержащей только цикл. Когда вы делаете &s
, вы получаете адрес этой локальной переменной, которая не связана с каким-либо из массивов.
Отличие от предыдущего цикла заключается в том, что там s
является указателем на первый элемент в массиве.
Чтобы объяснить это немного более "графически", то, что у вас есть в последнем цикле, похоже на
+----+ +---+ +------------+
| &s | ---> | s | ---> | family2[0] |
+----+ +---+ +------------+
То есть, &s
указывает на s
, а s
указывает на family2[0]
.
Когда вы делаете &s + 1
, у вас есть что-то вроде
+------------+
| family2[0] |
+------------+
^
|
+---+----
| s | ...
+---+----
^ ^
| |
&s &s + 1
Ответ 2
Картинки очень помогают:
+----------+
| "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 )
Адреса, выбранные в качестве случайных целых чисел для простоты
Дело в том, что хотя оба s
и family2[0]
указывают на один и тот же базовый адрес строкового литерала "father"
, указатели не связаны друг с другом и имеют свою собственную другую ячейку памяти, где они хранятся. *(&s+1) != family2[1]
.
Вы нажимаете UB, когда вы делаете *(&s + 1)
, потому что &s + 1
- это ячейка памяти, в которую вы не должны вмешиваться, т.е. она не принадлежит к любому созданному вами объекту. Вы никогда не знаете, что хранилось там = > Undefined Поведение.
Спасибо @2501 за то, что указали несколько ошибок!
Ответ 3
#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");
}
это пример, переработанный из вашего кода, если вы запустите его, вы найдете изменение адреса точки и семейства2, тогда вы поймете взаимосвязь цикла № 3.