Враховуючи стандартне визначення основної програми:
int main(int argc, char *argv[]) {
...
}
За яких обставин argc
у системі POSIX може бути нуль?
Враховуючи стандартне визначення основної програми:
int main(int argc, char *argv[]) {
...
}
За яких обставин argc
у системі POSIX може бути нуль?
argc
бути < 1
, якщо це саме 0
я знайшов тут port70.net/~nsz/c/c11/n1570.html#5.1.2.2.1p2
if (argc < x) { fprintf(stderr, "Usage: %s ...", argv[0]); }
, я однозначно бачу практичну актуальність цього питання :)
Відповіді:
Так, це можливо. Якщо ви викликаєте свою програму наступним чином:
execl("./myprog", NULL, (char *)NULL);
Або по черзі:
char *args[] = { NULL };
execv("./myprog", args);
Тоді в "myprog" argc
буде 0.
Стандарт також спеціально допускає 0, argc
як зазначено у розділі 5.1.2.2.1 щодо запуску програми в розміщеному середовищі:
1 Функція, що викликається під час запуску програми, називається
main
. Реалізація не оголошує прототипу для цієї функції. Він повинен визначатися з типом поверненняint
та без параметрів:
int main(void) { /* ... */ }
або з двома параметрами (тут іменуються
argc
іargv
, хоча можуть бути використані будь-які імена, оскільки вони є локальними для функції, в якій вони оголошені):
int main(int argc, char *argv[]) { /* ... */ }
або еквівалент; або якимось іншим способом, визначеним реалізацією.
2 Якщо вони оголошені, параметри
main
функції повинні відповідати наступним обмеженням:
- Значення
argc
має бути невід’ємним.argv[argc]
повинен бути нульовим покажчиком....
Зауважте також, що це означає, що якщо argc
0, то argv[0]
гарантовано буде NULL. Однак, як printf
трактується покажчик NULL, коли він використовується як аргумент %s
специфікатора, стандарт не визначає. У цьому випадку багато реалізацій виведуть "(null)", але я не вірю, що це гарантовано.
argc == 0
: OpenBSD.
exec
лише із шляхом та без аргументів для програми? (помилка? скопіювати шлях до нульового аргументу?)
execve
не вдається виконати помилку, EINVAL
якщо ви викликаєте її з порожнім argv
. Це легко пропустити на execve
сторінці керівництва , оскільки поведінка помилок для цієї умови згадується лише у списку можливих помилок внизу.
Щоб додати до інших відповідей, у C (POSIX чи ні) нічого не заважає викликати main () як функцію в програмі.
int main(int argc, int argv[]) {
if (argc == 0) printf("Hey!\n");
else main(0,NULL);
return 0;
}
Так, це може бути нуль, це означає argv[0] == NULL
.
Це конвенція, яка argv[0]
називається програмою. Ви можете мати, argc == 0
якщо запустите собі двійковий файл, як із сім’єю execve і не аргументуєте. Ви навіть можете вказати рядок, який ніде не буде назвою програми. Ось чому використання argv[0]
для отримання назви програми не зовсім надійне.
Зазвичай оболонка, куди ви вводите свій командний рядок, завжди додає назву програми як перший аргумент, але знову ж таки, це домовленість. Якщо argv[0]
== "--help" і ви використовуєте опцію getopt для синтаксичного аналізу, ви не виявите його, оскільки optind
він ініціалізований до 1, але ви можете встановити optind
значення 0, getopt
з'явиться опція use і "help" long.
коротенько: Це цілком можливо мати argc == 0
( argv[0]
не є насправді особливим). Це трапляється, коли панель запуску взагалі не дає аргументів.
Ранні пропозиції вимагали, щоб значення argc, передане main (), було "один або більше". Це зумовлено тією ж вимогою у проектах стандарту ISO C. Насправді, історичні реалізації передали значення нуля, коли жодним аргументам не надається виклику функцій exec. Ця вимога була вилучена зі стандарту ISO C, а згодом також вилучена з цього обсягу POSIX.1-2017. Формулювання, зокрема використання цього слова, вимагає від строго відповідного додатка POSIX передавати принаймні один аргумент функції exec, гарантуючи тим самим, що argc буде одним або більшим при виклику такою програмою. Насправді це хороша практика, оскільки багато існуючих додатків посилаються на argv [0] без попередньої перевірки значення argc.
Вимога щодо строго відповідаючої програми POSIX також говорить, що значення, передане як перший аргумент, має бути рядком імені файлу, пов’язаним із запущеним процесом. Хоча деякі існуючі програми в деяких випадках передають ім'я шляху, а не рядок імені файлу, рядок імені файлу є більш корисним, оскільки загальне використання argv [0] використовується для діагностики друку. У деяких випадках передане ім'я файлу не є фактичним ім'ям файлу; наприклад, багато реалізацій утиліти входу використовують домовленість префікса ('-') до фактичного імені файлу, що вказує інтерпретатору команд, що викликається, що це "оболонка входу".
Також зверніть увагу, що тест і [утиліти вимагають певних рядків для аргументу argv [0], щоб мати детерміновану поведінку у всіх реалізаціях.
Чи може argc дорівнювати нулю в системі POSIX?
Так, але це не буде суворо відповідати POSIX.
char *nothing = { 0 }; execve(*prog, nothing, nothing)
в системі, призведе до того, що POSIX оголосить цю систему "суворо невідповідною". Мені здається, що це навряд чи буде таким, як передбачав стандарт POSIX?
execve
системним викликом - але цей розробник робить невелику помилку і закликає нас без будь-яких аргументів: справедливо сказати, що система в цілому більше не є "суворо відповідає POSIX"?
всякий раз, коли ви хочете запустити будь-який виконуваний файл, як-от ./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)
.
(Однак я можу подумати про кілька неприємних причин, таких як використання вікон гонки синхронізації, пов'язаних з командами pipe або socket: злий процес надсилає злий запит через встановлений сокет домену 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 */
крім випадків, коли це вимагає хтось з урахуванням конкретного існуючого випадку використання. І навіть тоді я був би трохи підозрілим, бажаючи спочатку перевірити випадок використання.
write()
(функція C низького рівня; обгортка syscall в Linux), що повертає від'ємне значення, відмінне від -1. Це просто не повинно відбуватися, тому більшість програмістів не тестують на це. Однак це сталося в Linux через помилку файлової системи ядра для записів понад 2 ГіБ. (На даний час кількість записів обмежується трохи менше 2 ГіБ на рівні syscall, щоб уникнути подібних помилок в інших драйверах файлової системи.) Мій власний код - єдиний, який я бачив, який також перевіряє цей випадок. (Я сприймаю це як EIO
помилку.) Однак, як я вже сказав, я вирішив не перевіряти наявність argc == 0
свого коду.
argv[0]
коли argc == 0
? Хіба це не розмежування посилань на нульовий покажчик, що, у свою чергу, означає невизначену поведінку? Вам комфортно довіряти, що кожна реалізація C, з якою буде змушений ваш код, буде виконувати щось розумне?
int execv(const char *path, char *const argv[]);
з цимargv
містити лишеNULL
вказівник;)