Как ведут себя optind и argv в C?

Я читал Head First C и в настоящее время застрял в понимании int main(int argc, char *argv[]) и optind переменной getopt(). Меня беспокоит та же программа, что и в Как присваивается "optind" в C?, к сожалению, я до сих пор не могу понять, что происходит, даже после прочтения этого вопроса и справочных страниц. Мой код выглядит следующим образом:

#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    char *delivery = "";
    int thick = 0;
    int count = 0;
    char ch;

    printf("optind before loop is %i\n", optind);

    while ( (ch = getopt(argc, argv, "d:t")) != EOF)
    {
        printf("optind before switch is %i\n", optind);
        switch(ch)
        {
            case 'd':
                delivery = optarg;
                printf("argc in case d is %i\n", argc);
                printf("argv in case d is %c\n", *argv);
                printf("optind in case d is %i\n", optind);
                break;
            case 't':
                thick = 1;
                printf("argc in case t is %i\n", argc);
                printf("argv in case t is %c\n", *argv);
                printf("optind in case t is %i\n", optind);
                break;
            default:
                fprintf(stderr, "Unknown option: '%s'\n", optarg);
                return 1;
        }
        printf("after switch optind is %i\n", optind);
        printf("----------------\n");
    }

    printf("after loop argc is %i\n", argc);
    printf("after loop argv is %c\n", *argv);
    printf("after loop optind is %i\n", optind);
    printf("----------------\n");

    argc -= optind;
    argv += optind;

    printf("final argc is %i\n", argc);
    printf("final argv is %c\n", *argv);
    printf("final optind is %i\n", optind);

    if (thick) puts("Thick crust.");
    if (delivery[0]) printf("To be delivered %s.\n", delivery);

    puts("Ingredients:");

    for (count = 0 ; count < argc; count++)
    {
        puts(argv[count]);
    }
    return 0;
}

Я запустил программу с помощью ./order_pizza bacon -d now -t shrimp и получил следующий результат:

optind before loop is 1
optind before switch is 4
argc in case d is 6
argv in case d is O
optind in case d is 4
after switch optind is 4
----------------
argc in case t is 6
argv in case t is O
optind in case t is 5
after switch optind is 5
----------------
after loop argc is 6
after loop argv is O
after loop optind is 4
----------------
final argc is 2
final argv is ]
final optind is 4
Thick crust.
To be delivered now.
Ingredients:
bacon
pineapple

Код работает нормально, но я не до конца понимаю его внутренности. Вот мои проблемы:

  1. char *argv[] в main — это массив указателей, в котором каждый указатель указывает на первый символ соответствующей строки аргумента, а argv на самом деле argv[0] верно? А argv[0] — это адрес первого символа ./order_pizza, то есть .. Но почему мой вывод говорит, что argv был первым O, а затем ] (символы, которые даже не существуют в моем вводе)?

  2. Согласно man-странице getopt(), «getopt() переставляет содержимое argv при сканировании, так что в конечном итоге все неопции оказываются в конце». Означает ли это, что после сканирования argv становится {"./order_pizza","-d","now","-t","bacon","shrimp"}? Будет ли порядок параметров и аргументов оставаться прежним (например, можно ли вместо этого преобразовать переставленный массив в {"./order_pizza","-t","now","-d","shrimp","bacon"})?

  3. После цикла whileoptind равно 4, поэтому argv += optind должно быть эквивалентно argv[0] = argv[0] + 4;, что для меня выглядит как добавление 4 к адресу . (первый символ ./order_pizza). Учитывая, что массив теперь переставлен в {"./order_pizza","-d","now","-t","bacon","shrimp"}, не должны ли мы вместо этого попытаться сделать argv[0] = argv[4]; или argv = arg[optind]?

  4. Вектор аргумента начинается с элемента ./order_pizza. На странице руководства говорится: «Если больше нет символов параметров, getopt () возвращает -1. Тогда optind — это индекс в argv первого элемента argv, который не является параметром». Поскольку после обработки вектор представляет собой {"./order_pizza","-d","now","-t","bacon","shrimp"}, почему optind равно 4 (индекс bacon) вместо 0 (индекс ./order_pizza). Я знаю, что нам нужно первое, но разве ./order_pizza не считается элементом argv?

  5. Head First C утверждает, что «optind хранит количество строк, прочитанных из командной строки, чтобы обойти параметры», что, похоже, противоречит справочной странице getopt(), в которой указано, что optind — это порядковый номер. Это недосмотр авторов книги, или я что-то упустил и авторы действительно правы?

Ответы на старый вопрос об этой программе мне очень помогли, но у меня все еще есть проблемы, перечисленные выше. Спасибо!

В сторону: стандарт C указывает, что EOF является отрицательным, а не обязательно -1, несмотря на то, что большинство реализаций определяют его как. Так что тестируйте против -1, а не EOF.

Haris 12.02.2023 15:07

Эти printfs() вызывают неопределенное поведение. %c ожидает char, а не char *.

Haris 12.02.2023 15:18

OT, но есть некоторые другие проблемы с вашим кодом. Учитывая char ch; сравнение (ch = getopt(argc, argv, "d:t")) != EOF неверно. Во-первых, getopt() возвращает int, а не char, а EOF не может быть представлено как значение char. (Во всяком случае, при условии sizeof(char)<sizeof(int)). Кроме того, getopt() возвращает -1, а не EOF.

Andrew Henle 12.02.2023 17:57

(продолжение) EOF не равно -1: "EOF которое расширяется до целочисленного константного выражения с типом int и отрицательным значением" Использование неправильных типов и значений - отличный способ ввести тонкие ошибки, которые может быть очень сложно исправить. найти причину.

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

Ответы 1

Ответ принят как подходящий
  1. char *argv[] в main это массив указателей,

На самом деле это указатель на первый char * в таком массиве. Причуда C заключается в том, что синтаксис массива можно использовать в списке параметров функции для указания указателя. Было бы эквивалентно — только в списке параметров функции — объявить argv как char **argv. И некоторые люди делают.

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

Да.

и argv на самом деле argv[0] верно?

Нет. argv[0] имеет на один уровень косвенности меньше, чем argv. Они не могут относиться к одному и тому же объекту. Для любого действительного указателя pp[0] эквивалентен *p. Это включает argv: argv[0] эквивалентно *argv, а не самому argv.

А argv[0] это адрес первого символа ./order_pizza, то есть ..

Да, когда вы запускаете программу с помощью показанной команды.

Но почему мой вывод говорит, что argv был первым O, затем ] (символы, которые даже не существуют в моем вводе)?

Это не говорит об этом. Ваша программа ведет себя неопределенно, когда пытается напечатать *argv, char * (см. выше) с помощью printf спецификатора преобразования %c, который предназначен для значений типа char.

Хороший компилятор C предупредит вас об этом несоответствии. Если у вас нет предупреждения, то либо включите уровень предупреждения, либо получите лучший компилятор.


  1. Согласно справочной странице getopt(), «getopt() переставляет содержимое argv при сканировании, так что в конечном итоге все неопции оказываются в конце».

Обратите внимание, что это характеристика поведения по умолчанию реализации GNU getopt(), которая отличается в этом отношении от поведения, указанного POSIX (то есть просто остановить обработку опции, когда найден первый аргумент, не являющийся опцией).

Означает ли это, что после сканирования argv становится {"./order_pizza","-d","now","-t","bacon","shrimp"}?

В вашей примерной команде "./order_pizza" — это имя команды, «-d» и "now" — это параметр и его параметр-аргумент, а "-t" — еще один параметр (который не принимает аргумент). Остальные элементы argv, "bacon" и "shrimp" являются необязательными аргументами, и именно они переставляются до конца.

Будет ли порядок параметров и аргументов оставаться прежним (например, можно ли вместо этого преобразовать переставленный массив в {"./order_pizza","-t","now","-d","shrimp","bacon"})?

Документы не указывают, но я ожидаю, что относительный порядок необязательных аргументов будет сохранен. То есть я ожидаю первого порядка, который вы предлагаете, а не альтернативного порядка, который вы предлагаете. Если бы getopt() переупорядочил аргументы, не являющиеся параметрами, то это было бы неподходящим для множества распространенных идиом команд. Например, было бы очень плохо, если бы обработка getopt() преобразовала mv a b -f в mv -f b a. Однако такой аргумент — это не то же самое, что документация.


  1. После цикла while optind равно 4, поэтому argv += optind должно быть эквивалентно argv[0] = argv[0] + 4;,

Точно нет. argv — это не то же самое, что argv[0] (когда-либо). argv += optind эквивалентно argv = &argv[optind]. То есть он обновляет argv, чтобы он указывал на (указатель на) первый аргумент без опций.


  1. Вектор аргумента начинается с элемента ./order_pizza. На странице руководства говорится: «Если больше нет символов параметров, getopt () возвращает -1. Тогда optind — это индекс в argv первого элемента argv, который не является параметром». Поскольку после обработки вектор представляет собой {"./order_pizza","-d","now","-t","bacon","shrimp"}, почему optind равно 4 (индекс bacon) вместо 0 (индекс ./order_pizza).

Текст немного вводит в заблуждение. Вместо «первый элемент argv, который не является параметром», было бы точнее сказать «первый аргумент, не являющийся параметром, или завершающий нулевой указатель, если нет аргументов, не являющихся параметрами». Это также согласуется с остальным описанием того, как getopt() поддерживает значение optind. И делать это имеет смысл, хотя использовать это в качестве основного критерия небезопасно.

Я знаю, что нам нужно первое, но разве ./order_pizza не считается элементом argv?

Да, ./order_pizza — это элемент argv, но это не то, что здесь имеется в виду в руководстве.


  1. Head First C заявляет, что «optind хранит количество строк, считанных из командной строки, чтобы обойти параметры», что кажется конфликт с справочной страницей getopt(), в которой указано, что optind является индексом число. Это недосмотр авторов книги, или я пропустил что-то и авторы действительно правы?

Книга и страница руководства не конфликтуют, хотя книга немного слабее применительно к версии GNU getopt(). Предположим, что команда была дана как

./order_pizza -d now -t bacon shrimp

После того, как getopt() обработает все параметры, что определяется возвратом -1, значение optind будет равно 4. Это индекс первого аргумента, не являющегося параметром, "bacon", в argv. Это также количество предшествующих строк в argv. Вот что значит книга.

На самом деле книга точно подходит для POSIX-совместимого getopt(), но не совсем корректна для поведения по умолчанию версии GNU getopt(). Это связано с тем, что GNU getopt() имеет поведение, которое мы уже обсуждали, просматривая весь список аргументов в поисках опций вместо того, чтобы останавливаться на первом аргументе без опций. Тем не менее, идея, которую он пытается передать, та же самая: после того, как getopt() обработает все варианты, optind сообщает вам, где в argv начинаются аргументы, не являющиеся параметрами.

Спасибо большое, вы прояснили мою голову! Теперь я знаю, что забыл, что все переменные имеют свои адреса, поэтому на самом деле *argv == argv[0] и argv == &argv[0], что означает, что значение argv является адресом argv[0], но не сам элемент argv[0], а значение of argv[0] — это адрес первого символа первой строки. Еще раз спасибо!

NoobAdmin 18.02.2023 13:57

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