Учитывая стандартное определение основной программы:
int main(int argc, char *argv[]) {
...
}
При каких обстоятельствах argc может быть нулевым в системе POSIX?
стандарт C позволяет argc быть < 1, если это именно 0, я нашел его здесь port70.net/~nsz/c/c11/n1570.html#5.1.2.2.1p2
Что касается тега language-lawyer: как уже упоминалось, язык позволяет это. POSIX - это не совсем определение языка, но я сомневаюсь, что он явно запрещает его, см. Мой первый комментарий о том, что предполагать может привести к argc == 0.
Учитывая широко используемый if (argc < x) { fprintf(stderr, "Usage: %s ...", argv[0]); }, я определенно вижу практичный актуальность этого вопроса :)
было бы эффективным обманом stackoverflow.com/questions/8113786/…, если бы не точное условие, что это должно быть о POSIX
Другой кандидат на дублирование может быть: Когда argv [0] может иметь значение NULL?
Ирония заключается в том, что идиому можно разумно изменить для решения проблемного случая, добавив всего шесть следующих символов: if (argc < x) { fprintf(stderr, "Usage: %s ...", argv[0] || ""); }. (Выбор пустой строки или другого значения по умолчанию, конечно, дело вкуса.)
@mtraceur argv[0] || "", к сожалению, является int ...
@ WumpusQ.Wumbley Хороший улов, ты абсолютно прав. Это то, что я получаю за запуск фрагментов кода "от бедра", так сказать.
Спасибо всем за отличные комментарии и ответы. @FelixPalmen Именно поэтому я и спрашивал об этом!
@FelixPalmen По крайней мере, с glibc, которая на самом деле все еще работает и выводит Usage: (null) .... Но, тем не менее, это наверняка станет сюрпризом для многих разработчиков. И, возможно, где-то есть уязвимый исполняемый файл SUID, который можно использовать, установив argv[0] на NULL.
@FelixPalmen POSIX указывает Аргумент arg0 должен указывать на имя файла, связанного с процессом, запускаемым одной из функций exec. (Примечание: arg0 - второй аргумент execl, тот же оператор для execv).
@ Jean-BaptisteYunès уверен, но я не считаю "должен" жестким требованием;)
@FelixPalmen Верно! POSIX говорит долженДля реализации, соответствующей POSIX.1-2017, описывает функцию или поведение, которое рекомендуется, но не является обязательным. Приложение не должно полагаться на наличие функции или поведения. Приложение, которое полагается на такую функцию или поведение, не может гарантировать переносимость между соответствующими реализациями. Для приложения описывает функцию или поведение, которое является рекомендуемой практикой программирования для оптимальной переносимости.





Да, это может быть ноль, что означает, что argv[0] == NULL.
По соглашению, argv[0] - это имя программы.
У вас может быть argc == 0, если вы запустите двоичный файл, как в случае с семейством Execve, и не приведете никаких аргументов. Вы даже можете указать строку, которая не соответствует имени программы. Поэтому использование argv[0] для получения имени программы не совсем надежно.
Обычно оболочка, в которой вы вводите свою командную строку, всегда добавляет имя программы в качестве первого аргумента, но, опять же, это соглашение. Если argv[0] == "--help" и вы используете Getopt для синтаксического анализа, вы не обнаружите его, потому что optind инициализирован равным 1, но вы можете установить optind в 0, использовать getopt, и появится длинная опция "help".
Короче говоря: вполне возможно иметь argc == 0 (argv[0] сам по себе не особенный). Бывает, когда лаунчер вообще не аргументирует.
"но вы можете установить optind в 0" - Непереносимо. POSIX сообщает: «Если приложение устанавливает optind в ноль перед вызовом Getopt (), поведение не определено».
Ху? Где ты это читал? Обычно я читаю руководство на linux.die, и ничего не сказано о неопределенном поведении, поэтому, если бы я мог обновить свою документацию, было бы здорово.
Это потому, что большинство системных страниц описывают поведение в этой системе, поэтому не обязательно документировать, что является специфичным для системы, а что нет. Я забываю, действительно ли разрешено ссылаться на официальную спецификацию напрямую, поэтому я собираюсь ссылаться только на unix.org/version4, с примечанием, что прямые ссылки легко доступны от многих других.
Стандарт гласит: «Если значение argc больше нуля, строка, на которую указывает argv [0], представляет имя программы; argv [0] [0] должен быть нулевым символом, если имя программы недоступно из окружающая среда ". Это не звучит так, как будто argv[0] = = "--help" можно разрешить.
Итак, '--help' - это допустимое имя файла, так почему же argv [0] не может быть '--help'?
Как я понимаю в ответе выше, "--help" должен быть первым вариантом, а не именем исполняемого файла. Если исполняемый файл - "--help", то, конечно, возможно.
... Нет связи между argv [0] и настоящим именем исполняемого файла. Ваша программа может быть "1.bin", если я выполню ее с помощью execve и предоставлю "2.exe", как вы думаете, что произойдет? Ничего такого. argv [0] будет «2.exe», и ваша программа будет работать как шарм. Это просто удобное соглашение, согласно которому argv [0] должно быть именем программы. Итак, если имя программы - «--help», и после первого getopt я сбрасываю optind на 0, тогда появится «--help». Я просто указываю, что да, argv [0] обычно является именем программы (даже функция Unix C, такая как getopt, учитывает это в count), но
у вас нет гарантии, что так будет всегда.
Формулировка «это конвенция» немного неточна. Программа, не соблюдающая это «соглашение», не соответствует POSIX (прочтите раздел документации «Обоснование»). Так что, хотя очень возможно сделать что-то другое, это не соответствует требованиям. Следует ли ожидать, что это произойдет (потому что это возможно) или нет, - вопрос спорный. Я в команде "не поддерживаю сломанное программное обеспечение".
Я согласен с этим. Лучше подчиняться, чем делать какие-то хакерские поступки, которые приведут только к путанице. Но что ж, такое могло существовать, так что будьте осторожны.
Применяется закон Постела - «будьте консервативны в том, что вы делаете, будьте либеральны в том, что вы принимаете от других».
@CharlesDuffy: Вот как мы застряли с супом из тегов (режим причуд HTML), спамом по электронной почте (злоупотребление SMTP) и обычным интернет-блокированием случайных стран (плохие толчки BGP). Если кто-то нарушает стандарт и нет исторических причин для этого, вы должны проиграть. Громко.
@Damon Обоснование не является нормативным, и это не первый случай, когда в обосновании делается утверждение, не подкрепленное нормативным текстом. Я не могу найти какой-либо нормативный текст, в котором говорится, что передача нулевого указателя для имени программы не соответствует требованиям.
@hvd: попробуйте стандарт, например связано в ответе Звездного врата. Он начинается с "arg0 должен указывать на строку имени файла", который, по общему признанию, говорит должен, а не должен. Но даже должен предполагает, что нулевой указатель не предусмотрен. Три страницы экрана ниже, в разделе «Обоснование», вы можете прочитать «Формулировка, в частности, использование слова следует, требует, чтобы приложение POSIX, строго соответствующее требованиям, передавало хотя бы один аргумент». То, что вы думаете об этом, зависит от его личного мнения / религии. Моя личная точка зрения: «Не допускайте несогласных с поощрять» (что вы делаете, поддерживая их).
@Damon (извиняюсь за неправильный предыдущий комментарий, удален и исправлен). Я связался со стандартом в более раннем комментарии. «следует» определяется как «Для приложения описывает функцию или поведение, которое рекомендуется практикой программирования для оптимальной переносимости». Это не требование, это просто означает рекомендацию поддерживать некоторые реализации, не относящиеся к POSIX на 100%. И логическое обоснование, опять же, не является нормативным.
@CharlesDuffy: Стандарты, которые должны учитывать диалекты, предшествующие им, должны настоятельно рекомендовать документам / программам указывать, какой диалект они используют; программы должны быть либеральными в отношении диапазон диалектов, которые они принимают, но рекомендуется отмечать вещи, которые не соответствуют заявленному диалекту. К сожалению, авторы стандарта C еще не осознали этот принцип.
Да, это возможно. Если вы вызываете свою программу следующим образом:
execl("./myprog", NULL, (char *)NULL);
Или поочередно:
char *args[] = { NULL };
execv("./myprog", args);
Тогда в "myprog" argc будет 0.
Стандарт также специально позволяет использовать 0 argc, как указано в разделе 5.1.2.2.1 относительно запуска программы в размещенной среде:
1 The function called at program startup is named
main. The implementation declares no prototype for this function. It shall be defined with a return type ofintand with no parameters:int main(void) { /* ... */ }or with two parameters (referred to here as
argcandargv, though any names may be used, as they are local to the function in which they are declared):int main(int argc, char *argv[]) { /* ... */ }or equivalent; or in some other implementation-defined manner.
2 If they are declared, the parameters to the
mainfunction shall obey the following constraints:
- The value of
argcshall be nonnegative.argv[argc]shall be a null pointer....
Также обратите внимание, что это означает, что если argc равен 0, то argv[0] гарантированно будет NULL. Однако то, как printf обрабатывает NULL-указатель при использовании в качестве аргумента для спецификатора %s, в стандарте не прописано. Многие реализации в этом случае будут выводить «(null)», но я не верю, что это гарантировано.
@SylvainLeroux Вам (и другим, читающим этот ответ позже) может быть интересно узнать, помимо документации об общем случае, что пару лет назад система один, которая стремится быть POSIX-совместимой, имеет фактически сделала невозможно запускать программы с argc == 0: OpenBSD.
@mtraceur, из любопытства: что делает OpenBSD, если вы вызываете exec только с путем и без аргументов для программы? (сбой? скопировать путь к нулевому аргументу?)
@ilkkachu В OpenBSD execve выдает ошибку EINVAL, если вы вызываете его с пустым argv. В Страница руководства execve легко пропустить, так как поведение ошибки для этого условия упоминается только в списке возможных ошибок внизу.
@mtraceur Интересно, насколько это позволяет FreeBSD делает.
@mtraceur Очень интересно. Интересно, есть ли какие-нибудь проприетарные POSIX-сертифицированные системы * nix, которые сделали тот же выбор, что и OpenBSD.
Обратите внимание, что приведение NULLдолжно быть; он будет иметь неправильное представление, если NULL явно не приведен как void *.
@AnttiHaapala Правильно. Обновлено, чтобы отразить.
@AnttiHaapala Не могли бы вы пояснить, что вы имеете в виду? Сначала я подумал, что вы имели в виду, что должно быть явное приведение между char * и / или void *, но, насколько я знаю, char * и void * имеют одинаковое представление и макрос NULL обычно представляет собой простой 0 или преобразованный 0 в void *, согласно определению "константы нулевого указателя", поэтому я думаю, что не понимаю, что нужно преобразовать к чему здесь?
@mtraceur - NULL в части varargs. 0 будет int, а не указателем.
Разве пример execl не должен быть execl("./myprog", (char *)NULL);?
@Barmar Нет, подпись - int execl(const char *path, const char *arg, ...);. Второй аргумент - это известный const char *, поэтому его не нужно приводить, а отсутствие каких-либо дополнительных параметров дает предупреждение «недостаточно аргументов переменных для соответствия дозору».
Early proposals required that the value of argc passed to main() be "one or greater". This was driven by the same requirement in drafts of the ISO C standard. In fact, historical implementations have passed a value of zero when no arguments are supplied to the caller of the exec functions. This requirement was removed from the ISO C standard and subsequently removed from this volume of POSIX.1-2017 as well. The wording, in particular the use of the word should, requires a Strictly Conforming POSIX Application to pass at least one argument to the exec function, thus guaranteeing that argc be one or greater when invoked by such an application. In fact, this is good practice, since many existing applications reference argv[0] without first checking the value of argc.
The requirement on a Strictly Conforming POSIX Application also states that the value passed as the first argument be a filename string associated with the process being started. Although some existing applications pass a pathname rather than a filename string in some circumstances, a filename string is more generally useful, since the common usage of argv[0] is in printing diagnostics. In some cases the filename passed is not the actual filename of the file; for example, many implementations of the login utility use a convention of prefixing a ( '-' ) to the actual filename, which indicates to the command interpreter being invoked that it is a "login shell".
Also, note that the test and [ utilities require specific strings for the argv[0] argument to have deterministic behavior across all implementations.
Can argc be zero on a POSIX system?
Да, но это не будет строго соответствовать POSIX.
Означает ли «система POSIX» «каждая отдельная программа в системе является« строго соответствующим POSIX-приложением »? Правдивый вопрос - я не знаю педантичной семантики стандарта по этому поводу. Я знаю, что могу взять систему, сертифицированную как систему POSIX, и тривиально написать / запустить на ней программу, которая не является «строго соответствующим приложением POSIX», и я не уверен, что это предполагаемое значение Стандарт POSIX, чтобы сказать, что система перестает соответствовать требованиям в тот момент, когда на нее устанавливается какое-либо не строго соответствующее стороннее приложение?
@mtraceur Разве это не отдельный вопрос? Не уверен, ожидаете ли вы ответа в этой ветке комментариев или хотите, чтобы к ответу была добавлена дополнительная информация. Оба кажутся странными.
@mtraceur, если ваша программа работает в Linux, вы можете ожидать, что ваша программа будет работать в соответствии со стандартом Posix, ответственность ложится на программу, которая выполняет вас, чтобы соответствовать Posix, также как и ваша оболочка в целом. Тем не менее, ничто не мешает оболочке не соответствовать posix, но я очень сомневаюсь, что кто-то будет ее использовать;), если ваш дистрибутив Linux допускает несовместимость программы Posix в своих пакетах, это не проблема Linux.
@pipe Я прошу разъяснений по этому поводу, потому что я думаю, что он определит, применимо ли неявное рассуждение в этом ответе к вопросу или вводит в заблуждение: мне кажется, что ответ «Да, но это не будет строго соответствовать POSIX» чтобы логически подразумевать, что простое присутствие (или, возможно, выполнение) любой программы, которая может запускать char *nothing = { 0 }; execve(*prog, nothing, nothing) в системе, приведет к тому, что POSIX объявит эту систему «не совсем соответствующей». Мне кажется, что это вряд ли соответствует задумке стандарта POSIX?
@Stargateur Можно ли этого ожидать? Допустим, мы пишем программу на C, которая является «строго соответствующим POSIX-приложением», и запускаем ее в Linux, FreeBSD или одной из самых последних систем HP-UX или Solaris (в любой другой Unix, совместимой с POSIX). Итак, у нас есть наше строго соответствующее POSIX приложение в системе POSIX: теперь приходит какой-то другой разработчик, пишет небольшую программу, которая вызывает нашу программу с системным вызовом execve, но этот разработчик делает небольшую ошибку и вызывает нас без каких-либо аргументов: Будет ли справедливо сказать, что система в целом больше не «строго соответствует POSIX»?
@Stargateur У вас есть заявление thatif задом наперед. В Linux работает множество программ, но не более общие системы POSIX; почти любой дистрибутив будет включать модули ядра или программы, которые используют возможности или пространства имен процессов или что-то в этом роде. Практически все, что POSIX должно работать в Linux (хотя есть некоторые несовместимости); обратное неверно.
всякий раз, когда вы хотите запустить любой исполняемый файл, например ./a.out, у него будет один аргумент - program name. Но можно запустить программу с argc как zero в Linux, запустив ее из другой программы, которая вызывает execv с empty argument list.
например, для
int main() {
char *buf[] = { NULL };
execv("./exe", buf); /* exe is binary which it run with 0 argument */
return 0;
}
TL; DR: Да, argv[0] может быть NULL, но не по какой-либо хорошей / разумной причине, о которой я знаю. Однако есть причины не заботиться о том, имеет ли argv[0] значение NULL, и, в частности, разрешить сбой процесса, если это так.
Да, argv[0] может иметь значение NULL в системе POSIX, если и только если он был выполнен без каких-либо аргументов.
Более интересный практический вопрос - должна ли ваша программа заботиться.
Ответ на этот вопрос - "Нет, ваша программа может предполагатьargv[0]не NULL ", потому что некоторые системные утилиты (утилиты командной строки) либо не работают, либо работают недетерминированно, как argv[0] == NULL, но, что более важно, нет веской причины (кроме глупости или гнусности). цели), почему какой-либо процесс будет делать это. (Я не уверен, что стандартное использование getopt() также не сработает, но я не ожидал, что это сработает.)
Большая часть кода и большинство примеров и утилит, которые я пишу, начинаются с эквивалента
int main(int argc, char *argv[])
{
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
printf("Usage: %s [ -h | --help ]\n", argv[0]);
/* ... print usage ... */
return EXIT_SUCCESS;
}
и это разумный, и это приемлемо, потому что у процесса нет веских причин для выполнения другого процесса без предоставления по крайней мере пути выполняемой команды, то есть execlp(cmd, cmd, NULL), а не execlp(cmd, NULL).
(Тем не менее, я могу придумать несколько гнусных причин, таких как использование окон временных гонок, связанных с командами конвейера или сокета: злой процесс отправляет злой запрос через установленный сокет домена Unix, а затем немедленно заменяет себя авторизованной командой жертвы (запустить без каких-либо аргументов, чтобы обеспечить минимальное время запуска), так что когда служба, получающая запрос, проверяет учетные данные однорангового узла, она видит команду жертвы, а не исходный вредоносный процесс.Это, на мой взгляд, лучше всего для такой жертвы команды для резкого и быстрого сбоя (SIGSEGV, разыменовывая NULL-указатель), вместо того, чтобы пытаться вести себя «хорошо», давая злому процессу большее временное окно.)
Другими словами, хотя процесс заменяет себя другим процессом является возможный, но без каких-либо аргументов, приводящих к нулевому значению argc, такое поведение является неразумным в строгом смысле слова, когда нет известной не гнусной причины для этого.
Из-за этого, а также из-за того, что я люблю усложнять жизнь гнусным и равнодушным программистам и их программам, я лично никогда не добавлю тривиальную проверку, подобную
static int usage(const char *argv0)
{
/* Print usage using argv0 as if it was argv[0] */
return EXIT_SUCCESS;
}
int main(int argc, char *argv[])
{
if (argc < 1)
return usage("(this)");
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
return usage(argv[0]);
/* argv[0] and argv[1] are non-NULL, argc >= 2 */
кроме случаев, когда этого требует кто-то, имеющий в виду конкретный существующий вариант использования. И даже тогда я был бы немного подозрительным, если бы сначала захотел сам проверить этот вариант использования.
Считаете ли вы, что проверка программ, чтобы убедиться, что они были написаны с особым вниманием к крайним случаям, хорошим / разумным / не гнусным вариантом использования? Потому что я регулярно вызываю каждую новую программу, которую использую, с нулевыми аргументами для быстрой оценки того, какого поведения я могу ожидать от программы в других обстоятельствах «не должно происходить / не должно происходить». У меня даже есть инструмент командной строки, так что я могу делать это быстро и легко, без необходимости каждый раз писать для него код (хотя, по общему признанию, я написал инструмент больше для управления нулевым аргументом в целом).
@mtraceur: В целом да, но в данном случае нет. Это просто потому, что я думаю, что у случая без аргументов нет подходящих вариантов использования (о которых я знаю), но есть по крайней мере один гнусный вариант использования; и этот гнусный вариант использования лучше всего побеждать, если программа умирает как можно раньше (и благодаря SIGSEGV это хороший способ сделать это). Я не думаю, что проверка случая без аргументов является каким-либо указанием на то, что программа в противном случае ведет себя разумно в ситуациях типа "не должно происходить".
@mtraceur: одним из примеров является write() (низкоуровневая функция C; оболочка системных вызовов в Linux), возвращающая отрицательное значение, отличное от -1. Этого просто не должно происходить, поэтому большинство программистов не проверяют это. Тем не менее, это произошло в Linux из-за ошибки файловой системы ядра при записи более 2 ГиБ. (В настоящее время запись ограничена чуть менее 2 ГиБ на уровне системных вызовов, чтобы избежать подобных ошибок в других драйверах файловой системы.) Мой собственный код - единственный, который я видел, который также проверяет этот случай. (Я рассматриваю это как ошибку EIO.) Тем не менее, как я уже сказал, я решил не проверять наличие argc == 0 в моем коде.
Я решил добавить +1 к вашему ответу, не говоря уже о нашем обсуждении, потому что я считаю, что ваша точка зрения и подход ценны. Я хочу, чтобы больше людей так критически и внимательно думали о том, следует ли обрабатывать (или не обрабатывать) весь спектр угловых случаев, которые могут произойти.
Поправьте меня, если я ошибаюсь: действительно ли стандарт C гарантирует какое-либо разумное поведение, не говоря уже о том, чтобы умереть как можно раньше, если вы разыменовываете argv[0], когда argc == 0? Разве это не разыменование нулевого указателя, что, в свою очередь, означает неопределенное поведение? Комфортно ли вы верить, что каждая реализация C, с которой будет работать ваш код, будет выполнять что-то разумное?
Что касается того, коррелирует ли проверка случая без аргументов с тем, будет ли программа в противном случае вести себя разумно в угловых случаях: как вы думаете, большинство людей, которые не проверяют наличие argc == 0, делают это с вашей позиции, или как часть общей схемы игнорирования угла? случаи? Или еще один аспект: кодировщик, который подходит к вещам так же внимательно, как и вы, может решить или не решить этот случай, но неосторожный кодер почти наверняка не справится с этим - какой тип кодировщика более распространен? Учитывая только ваш код без вашего объяснения, я не могу знать, что это было.
@mtraceur: re. разыменование NULL: нет, никаких гарантий, только практическое наблюдение. Да, я действительно ожидаю, что все нормальные реализации C прервут процесс при разыменовании указателя NULL. Нет, я не думаю, что проверка argc == 0 на что-то указывает, поэтому я не думаю, что она вообще полезна в качестве индикатора / теста. Лучшим тестом было бы вставить read() и write() и посмотреть, что происходит, когда возникают реальные ошибки (короткие чтения / записи, как может случиться с конвейерами или сокетами); или когда malloc() / realloc() не работает с NULL. Это может свидетельствовать об общем отношении разработчиков.
Позвольте нам продолжить обсуждение в чате.
Чтобы добавить к другим ответам, в C (POSIX или нет) нет ничего, что препятствовало бы вызову main () как функции в программе.
int main(int argc, int argv[]) {
if (argc == 0) printf("Hey!\n");
else main(0,NULL);
return 0;
}
... Хм. Я думал, что это специально запрещено, но оказалось, что это делает только C++, и C это нормально.
Я бы попробовал
int execv(const char *path, char *const argv[]);сargv, содержащим только указательNULL, чтобы узнать;)