#c
#c
Вопрос:
У меня есть эти функции, и я хотел бы знать, может ли кто-нибудь мне помочь. Я должен исследовать, почему они вызывают «ошибку segfault», и почему это происходит быстрее или медленнее в зависимости от его условий. Я предположил, что в Rec1 это вызвано бесконечным циклом, который сжимает память стека памяти. В rec2, я полагаю, это происходит быстрее из-за того же условия, что и в Rec1, но добавляя, что он также каждый раз выделяет память для указателя. В Rec3 () это приводит к мгновенному сбою, поскольку на второй итерации выделяется одно и то же место памяти, и вызывает проблему, поскольку программа пытается получить доступ к той же выделенной памяти. В Rec4 () я думаю, это вызвано тем, что она создает массив с бесконечными позициями, спросите, является ли ограничение максимального пространства массива. Можете ли вы дать мне несколько советов по поводу этих предположений?
#include <stdio.h>
#include <stdlib.h>
#define MOD 10000
int k = 0;
char global[100];
void
panic (char *m)
{
fprintf (stderr, "%sn", m);
exit (0);
}
void
rec1 ()
{
k ;
if (k % MOD == 0)
fprintf (stderr, "%d ", k / MOD);
rec1 ();
}
void
rec2 ()
{
int *tmp;
tmp = malloc (100 * sizeof (int));
if (tmp == NULL)
exit (0);
k ;
if (k % MOD == 0)
fprintf (stderr, "%d ", k / MOD);
rec2 ();
}
void
rec3 ()
{
int tmp[100];
k ;
if (k % MOD == 0)
fprintf (stderr, "%d ", k / MOD);
rec3 ();
}
void
rec4 ()
{
global[k] = k;
if (k % 200 == 0)
fprintf (stderr, "%d ", k);
k ;
rec4 ();
}
int
main (int argc, char *argv[])
{
int mode = 1;
if (argc > 1)
mode = atoi (argv[1]);
printf ("Testing rec%d...n", mode);
switch (mode)
{
case 1:
rec1 ();
break;
case 2:
rec2 ();
break;
case 3:
rec3 ();
break;
case 4:
rec4 ();
break;
default:
panic ("Wrong mode");
}
return 0;
}
Это результат, когда я запускаю скомпилированную программу C в терминале.
Testing rec1...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
Program received signal SIGSEGV, Segmentation fault.
0x0000555555554904 in rec1 () at stack.c:24
24 rec1 ();
Testing rec2...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7a7b96a in __GI___libc_free (mem=0x555555757670) at malloc.c:3086
3086 malloc.c: No existe el archivo o el directorio.
Testing rec3...
1
Program received signal SIGSEGV, Segmentation fault.
0x0000555555554a43 in rec3 () at stack.c:53
53 rec3 ();
Testing rec4...
0 200 400 600 800 1000 1200 1400 1600 1800 2000 2200 2400 2600 2800 3000 3200 3400 3600 3800 4000 Violación de segmento (`core' generado)
Program received signal SIGSEGV, Segmentation fault.
0x0000555555554a1f in rec4 ()
Комментарии:
1. Я не вижу,
main()
чтобы узнать, как вызываютсяrec1()
orrec2()
. Я не знаю, чтоk
такое. Я вижу рекурсивные функции, которые, вероятно, раздувают ваш стек. Как правило, при сбое вы открываете программу в отладчике и проверяете конечное состояние программы. Это, с практикой, объясняет вам, почему программа завершилась сбоем.2. они вылетают, потому что вы не включили оптимизацию компиляции. Оба оптимизируются для конечных вызовов, которые не приводят к утечке памяти
3. Вы знаете, что означает название этого сайта?
4. @AnttiHaapala — Любая программа, которая требует оптимизации для работы, пугает меня до чертиков 🙂
5. @MichaelDorgan На самом деле это забавно. Насколько я могу судить, этот код не содержит UB как таковой , но все еще зависит от оптимизации. Возможно, я что-то упускаю..
Ответ №1:
По моему опыту, код, который у вас есть, с большой вероятностью вызовет ошибку. Без какой-либо обратной связи с компилятором или программой немного сложно определить, что именно пошло не так, но я полагаю, что вы можете искать (как правило) информацию о стеках, кучах и рекурсии.
Во-первых, пожалуйста, обратите внимание, что
void rec1 () {
k ;
if (k % MOD == 0)
fprintf (stderr, "%d ", k / MOD);
rec1 ();
}
это НЕ «итерация». Итерация относится к повторению последовательной части кода (обычно это цикл for
или while
). То, что у вас здесь есть, — это рекурсия. Рекурсия создает новый экземпляр метода для работы вместе с указателем стека для перехода к последней точке выполнения (а также для сохранения любых непосредственно относящихся к делу переменных). Это происходит каждый раз, когда вы вызываете rec1 ()
функцию из своей rec1 ()
функции, в конце концов, у вас закончится место в стеке для хранения этих указателей. Количество указателей, которые вы можете хранить в стеке, обычно довольно велико на современных компьютерах, но, учитывая, что у вас нет return
инструкции, вы в конечном итоге исчерпаете максимальную емкость.
Редактировать
Этот пост был отредактирован, чтобы отразить новый материал, представленный вопросом.
Хорошо…Из представленного вами материала похоже, что вас, по сути, спрашивают о том, где каждый rec
хранит и обрабатывает информацию…
В случае Rec1
это действительно кажется простым случаем переполнения стека. Указатель на последнюю точку выполнения предыдущего Rec1 сохраняется в стеке, что в конечном итоге приводит к сбою программы примерно после 520 000 экземпляров. Учитывая, что каждый указатель равен 4 байтам, это около 2 МБ только рекурсивной информации об указателе, хранящейся в вашем стеке, прежде чем он разрушится и вызовет ошибку Seg из-за переполнения стека.
Второй случай немного сложнее. Обратите внимание, что ваша программа указывает, что она совершает примерно 260 000 рекурсий, прежде чем она вызовет ошибку Seg. Это ровно половина Rec1. ОДНАКО это не обязательно переполнение стека как таковое. Rec2 выделяет 400 байт данных в куче за рекурсию. Указатель на кучу хранится в стеке, что означает, что на каждую рекурсию в стеке сохраняется 8 байт (что может быть связано с тем, почему его ровно половина, но также может быть объяснено соотношением вашего размера стека / кучи). Теперь ошибка для Rec2 гласит, что malloc
не удалось найти файл или каталог, что, как мне кажется, как будто malloc не может завершиться правильно. На самом деле это может указывать на то, что был достигнут максимальный размер кучи.
Rec3
довольно просто. Весь массив целых чисел tmp сохраняется в стеке для каждой рекурсии. это 4 байта на целое число, умноженное на 100 целых, что составляет 400 байт в стеке НА рекурсию. Неудивительно, что она завершается сбоем от 10 000 до 20 000 рекурсий. Просто не хватило места для хранения данных в стеке. ПРИМЕЧАНИЕ: В связи с тем, что вы упомянули в своем вопросе, этот массив tmp не пытается выделить ту же область памяти. Из-за того, что это рекурсивно построено, это создает новое пространство в стеке для этого экземпляра функции.
Rec4
это простой случай переполнения буфера. После перезаписи первых 100 байт памяти, выделенных в global[100],
, это было только вопросом времени, прежде чем k
это заставило бы global[k]
указывать на адресное пространство, ограниченное процессом. Это вызвало ошибку seg примерно после 4000 рекурсий (k не было по модулю 10000 в rec4
).
Я надеюсь, что это разъяснение поможет.
Комментарии:
1.Вообще не гарантируется, что это вызовет ошибку. В стандарте C нет ничего, что говорит «бесконечная рекурсия должна привести к сбою вашей программы». На самом деле, даже
rec2
скомпилированный с Clang и -O3, не приводит к утечке памяти и сбою2. Хорошо, теперь это лучше 😉
3. Мне следовало добавить больше информации @armitus. Утечка вызвана виртуальной машиной Ubuntu, которая компилируется с gcc и запускается с gbd. В любом случае, это результат
4. @armitus Спасибо за эту информацию. Я «играл» с размером стека в ubuntu, чтобы посмотреть, повлияет ли это на выполнение этих программ, и действительно, влияет.