Ошибка сегментации при выполнении следующей программы C

Эта программа на C принимает имена планет в качестве аргументов и выводит, являются ли они планетами или нет. Хороший кейс работает

./planets Venus Mercury

Но если я добавлю плохой случай, я получаю Segmentation Fault.

./planets Venus Mercury mercury

Что может быть причиной этого? Заранее спасибо.

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

#define NUM_PLANETS 9

int main(int argc, char* argv[]) {
    char *planets[] = { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Pluto" };
    int i, j;
    for (i = 1; i < argc; i++) {
        for (j = 0; j < NUM_PLANETS; j++) {
            if (strcmp(argv[i], planets[j]) == 0) {
                printf("%s is planet %d\n", argv[i], j + 1);
                break;
            }
        }
        if (j == NUM_PLANETS) {
            printf("%s is not a planet\n", argv[i]);
        }
    }
}
NUM_PLANETS равно 9, но в вашем массиве всего 8 планет.
pmacfarlane 09.08.2023 17:17

Ваш массив инициализирован 8 строками, но NUM_PLANETS равен 9, поэтому вы получаете доступ к одному элементу после конца массива. Я предлагаю заменить NUM_PLANETS на (sizeof(planets) / sizeof(planets[0])).

Bodo 09.08.2023 17:17

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

tadman 09.08.2023 17:20

количество планет должно быть 8, и последним должен быть Нептун, а не Плутон.

Tim Randall 09.08.2023 17:40

Кроме того... лучшее тестирование помогло бы. ./planets testname разбился бы точно так же

Tim Randall 09.08.2023 17:41

@raphire "Нептун" грустит из-за того, что его оставили на вечеринке. Отзыв char *planets[] = { ... }.

chux - Reinstate Monica 09.08.2023 23:53

@raphire: вы можете принять один из ответов, нажав на серую галочку под его оценкой.

chqrlie 10.08.2023 00:23

@chqrlie Спасибо за ответ. Я принял ответ

raphire 10.08.2023 17:13
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
0
8
90
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Код имеет неопределенное поведение, потому что инициализатор для массива planets имеет только 8 записей вместо 9, что является значением NUM_PLANETS. Когда строковый аргумент командной строки не найден среди 8 записей, вы сравниваете его с planets[8], который представляет собой доступ за пределы конца массива и, скорее всего, недопустимый указатель, вызывающий ошибку сегментации, когда он разыменовывается strcmp.

Вы должны добавить отсутствующую запись "Neptune" в инициализатор, и вы должны вычислить количество записей из размера массива.

Вот модифицированная версия:

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

int main(int argc, char *argv[]) {
    const char *planets[] = {
        "Mercury", "Venus", "Earth", "Mars", "Jupiter",
        "Saturn", "Uranus", "Neptune", "Pluto"
    };
    int num_planets = sizeof(planets) / sizeof(*planets);
    int i, j;
    for (i = 1; i < argc; i++) {
        for (j = 0; j < num_planets; j++) {
            if (strcmp(argv[i], planets[j]) == 0) {
                printf("%s is planet %d\n", argv[i], j + 1);
                break;
            }
        }
        if (j == num_planets) {
            printf("%s is not a planet\n", argv[i]);
        }
    }
    return 0;
}

Кроме того, вы можете добавить терминатор нулевого указателя в planets:

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

int main(int argc, char *argv[]) {
    const char *planets[] = {
        "Mercury", "Venus", "Earth", "Mars", "Jupiter",
        "Saturn", "Uranus", "Neptune", "Pluto", NULL
    };

    for (int i = 1; i < argc; i++) {
        for (int j = 0;; j++) {
            if (!planets[j]) {
                printf("%s is not a planet\n", argv[i]);
                break;
            }
            if (strcmp(argv[i], planets[j]) == 0) {
                printf("%s is planet %d\n", argv[i], j + 1);
                break;
            }
        }
    }
    return 0;
}

Должен или не должен Плутон присутствовать в массиве, спорно . Если включить Карликовые планеты, должны присутствовать и другие кандидаты: Эрида, Хаумеа, Макемаке, Гонгонг, Квавар, Седна, Церера, Оркус и Салация...

Вы получаете доступ к массиву за его пределами. Он вызывает неопределенное поведение.

Я бы использовал дозорное значение или вычислил размер массива.

1.

int main(int argc, char* argv[]) {
    char* planets[] = { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Pluto", NULL};
    int i, j;
    for (i = 1; i < argc; i++) {
        for (j = 0; planets[j]; j++) {
            if (strcmp(argv[i], planets[j]) == 0) {
                printf("%s is planet %d\n", argv[i], j + 1);
                break;
            }
        }
        if (!planets[j]) {
            printf("%s is not a planet\n", argv[i]);
        }
    }
}

https://godbolt.org/z/nMfdd5nf3

2.

#define NUM_PLANETS(list) (sizeof(list) / sizeof((list)[0]))

int main(int argc, char* argv[]) {
    char* planets[] = { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Pluto"};
    int i;
    size_t j;
    for (i = 1; i < argc; i++) {
        for (j = 0; j < NUM_PLANETS(planets); j++) {
            if (strcmp(argv[i], planets[j]) == 0) {
                printf("%s is planet %zu\n", argv[i], j + 1);
                break;
            }
        }
        if (j == NUM_PLANETS(planets)) {
            printf("%s is not a planet\n", argv[i]);
        }
    }
}

https://godbolt.org/z/71er14q3f

Есть какая-то конкретная причина использовать sizeof((list)[1]) вместо sizeof((list)[0])?

chqrlie 09.08.2023 23:44

@chqrlie Нет - это не имеет значения, поскольку разыменования нет

0___________ 10.08.2023 00:12

Конечно, это не имеет значения, поэтому можно было написать sizeof((list)[9]) или sizeof((list)[42]) для развлечения или sizeof 0[list], чтобы сохранить 3 символа, или даже без каких-либо жестко закодированных цифр: #define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a)[sizeof(a)])

chqrlie 10.08.2023 00:20

@chqrlie действительно

0___________ 10.08.2023 00:32
Ответ принят как подходящий

Вы объявили массив из 8 элементов

char *planets[] = { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Pluto" };

но в условии внутреннего цикла for

for (j = 0; j < NUM_PLANETS; j++) {

вы используете макрос NUM_PLANETS, для которого установлено значение 9

#define NUM_PLANETS 9

В результате в цикле for может быть доступ к памяти за пределами массива, что приводит к неопределенному поведению.

Использование макроса делает вашу программу подверженной ошибкам.

Вместо этого вы можете внутри main определить реальное количество элементов в массиве следующим образом

char *planets[] = { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Pluto" };
const size_t NUM_PLANETS = sizeof( planets ) / sizeof( *planets );

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

Второе замечание заключается в том, что использование оператора break во внутреннем цикле for делает вашу программу менее читабельной. Это просто плохой стиль программирования.

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

Вместо этого вы можете написать программу, например, следующим образом

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

int main(int argc, char* argv[]) 
{
    const char *planets[] = 
    { 
        "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Pluto" 
    };
    const size_t NUM_PLANETS = sizeof( planets ) / sizeof( *planets );

    for ( int i = 1; i < argc; i++ ) 
    {
        size_t j = 0;

        while ( j < NUM_PLANETS && strcmp( argv[i], planets[j] ) != 0 ) ++j;

        if ( j != NUM_PLANETS ) 
        {
            printf( "%s is planet %zu\n", argv[i], j + 1 );
        }
        else
        {
            printf( "%s is not a planet\n", argv[i] );
        }
    }
}

Обратите внимание на квалификатор const в спецификаторах типов в объявлении массива. Хотя в C строковые литералы имеют типы непостоянных массивов символов (по историческим причинам), тем не менее, любая попытка изменить строковый литерал приводит к неопределенному поведению. Так что такие массивы лучше определять как в программе с квалификатором const. Кроме того, в C++ строковые литералы имеют типы константных массивов символов. Таким образом, вы даже сможете скомпилировать свою программу с помощью компилятора C++.

Вы инициализировали массив для 8 планет, но во втором цикле for вы получаете доступ к 9 элементам. Это дает ошибку сегментации, поскольку вы пытаетесь получить доступ к данным, которые вы не выделили массиву, содержащему планеты.

Другие вопросы по теме