Эта программа на 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]);
}
}
}
Ваш массив инициализирован 8 строками, но NUM_PLANETS равен 9, поэтому вы получаете доступ к одному элементу после конца массива. Я предлагаю заменить NUM_PLANETS на (sizeof(planets) / sizeof(planets[0])).
Традиция C состоит в том, чтобы использовать NULL завершенный массив автоматического размера, а не предполагать, что это определенный размер.
количество планет должно быть 8, и последним должен быть Нептун, а не Плутон.
Кроме того... лучшее тестирование помогло бы. ./planets testname разбился бы точно так же
@raphire "Нептун" грустит из-за того, что его оставили на вечеринке. Отзыв char *planets[] = { ... }.
@raphire: вы можете принять один из ответов, нажав на серую галочку под его оценкой.
@chqrlie Спасибо за ответ. Я принял ответ



Код имеет неопределенное поведение, потому что инициализатор для массива 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 Нет - это не имеет значения, поскольку разыменования нет
Конечно, это не имеет значения, поэтому можно было написать sizeof((list)[9]) или sizeof((list)[42]) для развлечения или sizeof 0[list], чтобы сохранить 3 символа, или даже без каких-либо жестко закодированных цифр: #define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a)[sizeof(a)])
@chqrlie действительно
Вы объявили массив из 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 элементам. Это дает ошибку сегментации, поскольку вы пытаетесь получить доступ к данным, которые вы не выделили массиву, содержащему планеты.
NUM_PLANETSравно 9, но в вашем массиве всего 8 планет.