Предоставляет ли ISO C возможность наложения указателей argv [] на main()?

ISO C требует, чтобы размещенные реализации вызывали функцию с именем main. Если программа получает аргументы, они принимаются как массив указателей char*, второй аргумент в main определении int main(int argc, char* argv[]).

ISO C также требует, чтобы строки, на которые указывает массив argv были модифицируемыми.

Но могут ли элементы argv псевдонима друг друга? Другими словами, могут ли быть i, j такие, что

  • 0 >= я && я < argc
  • 0 >= j && j < argc
  • i != j
  • 0 < strlen(argv[i])
  • strlen(argv[i]) <= strlen(argv[j])
  • argv[i] aliases argv[j]

при запуске программы? Если это так, запись через argv[i][0] также будет видна через строку aliasing argv[j].

Соответствующие положения стандарта ISO C ниже, но не позволяют мне окончательно ответить на титульный вопрос.

§ 5.1.2.2.1 Запуск программы

Функция, вызванная при запуске программы, называется main. Реализация не объявляет прототипа для этой функции. Он должен быть определен с типом возврата int и без параметров:

int main(void) { /* ... */ }

или с двумя параметрами (называемыми здесь argc и argv, хотя любые имена могут использоваться, поскольку они являются локальными для функции, в которой они объявлены):

int main(int argc, char *argv[]) { /* ... */ }

или эквивалент; 10) или каким-либо другим способом реализации.

Если они объявлены, параметры main функции должны подчиняться следующим ограничениям:

  • Величина argc должна быть неотрицательной.
  • argv[argc] должен быть нулевым указателем.
  • Если значение argc больше нуля, члены массива argv[0] через argv[argc-1] включительно должны содержать указатели на строки, которым перед запуском программы заданы значения, определяемые реализацией среды хоста. Цель состоит в том, чтобы предоставить информацию о программе, определенную до запуска программы, из другого места в размещенной среде. Если среда хоста не способна снабжать строки буквами в верхнем и нижнем регистре, реализация должна гарантировать, что строки получены в нижнем регистре.
  • Если значение argc больше нуля, строка, на которую указывает argv[0] представляет собой имя программы; argv[0][0] должен быть нулевым символом, если имя программы недоступно в среде хоста. Если значение argc больше единицы, строки, на которые указывает argv[1] через argv[argc-1] представляют собой параметры программы.
  • Параметры argc и argv и строки, на которые указывает массив argv, могут быть модифицированы программой и сохраняют свои последние сохраненные значения между запуском программы и завершением программы.

По моему чтению ответ на титульный вопрос "да", поскольку нигде он не запрещен явно, и нигде стандартное требование или требование использования char* restrict* -qualified argv, но ответ может включить интерпретацию "и сохранить их последние сохраненные значения между запуском программы и завершением программы.".

Практический импорт этого вопроса заключается в том, что если ответ на него действительно "да", переносная программа, которая хочет изменить строки в argv сначала должна выполнить (эквивалент) POSIX strdup() для них.

Ответ 1

По моему чтению, ответ на титул "да", поскольку нигде он не запрещен явно, и нигде стандартное требование не требует или требует использования ограниченного аргумента argv, но ответ может включить интерпретацию "и сохранить их последние - сохраненные значения между запуском программы и завершением программы. ".

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

Практический импорт этого вопроса заключается в том, что если ответ на него действительно "да", переносная программа, которая хочет изменить строки в argv, сначала должна выполнить (эквивалент) POSIX strdup() для них.

Действительно, именно поэтому я считаю, что комитет даже не рассмотрел эту возможность. Если бы они это сделали, то, конечно же, они бы включили сноску к тому же самому эффекту или явно указали, что строки аргументов различны.

Я склонен думать, что эта деталь избегала внимания комитета, потому что на практике реализации действительно предоставляют различные строки, и потому что это редко, тем более, что программы модифицируют свои строки аргументов (хотя изменение самого argv несколько более распространено). Если комитет согласился опубликовать официальную интерпретацию в этой области, то я не удивлюсь, если они откажутся от возможности сглаживания.

Однако до тех пор, пока такая интерпретация не будет издана, вы правы в том, что строгое соответствие не позволяет вам полагаться априори на элементы argv которые не являются псевдонимом.

Ответ 2

То, как это работает на обычных платформах * nix (включая Linux и Mac OS, предположительно, FreeBSD), состоит в том, что argv представляет собой массив указателей в единую область памяти, содержащую строки аргументов один за другим (разделенные только нулевым терминатором). Использование execl() не меняет этого - даже если вызывающий абонент передает один и тот же указатель несколько раз, исходная строка копируется несколько раз, без специального поведения для идентичных (то есть псевдонимов) указателей (необычный случай, не имеющий большого преимущества для оптимизации).

Однако C не требует этой реализации. Поистине параноид может захотеть скопировать каждую строку перед ее модификацией, возможно, пропуская копии, если память ограничена, и цикл через argv показывает, что ни один из указателей фактически не псевдоним (по крайней мере, среди тех, которые программа намеревается изменить). Это кажется чересчур параноидальным, если вы не разрабатываете программное обеспечение для полетов и т.п.

Ответ 3

В качестве точки данных я скомпилировал и запустил следующие программы на нескольких системах. (Отказ от ответственности: эти программы предназначены для предоставления точки данных, но, как мы увидим, они не отвечают на вопрос, как указано).

p1.c:

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

int main()
{
    char test[] = "test";
    execl("./p2", "p2", test, test, NULL);
}

p2.c:

#include <stdio.h>

int main(int argc, char **argv)
{
    int i;
    for(i = 1; i < argc; i++) printf("%s ", argv[i]); printf("\n");
    argv[1][0] = 'b';
    for(i = 1; i < argc; i++) printf("%s ", argv[i]); printf("\n");
}

Каждое место, где я это пробовал (под MacOS и несколько разновидностей Unix и Linux), он напечатал

test test 
best test 

Так как вторая строка никогда не была " best best ", это доказывает, что в тестируемых системах к моменту запуска второй программы строки уже не сглаживаются.

Конечно, этот тест не доказывает, что строки в argv никогда не могут быть псевдонимом ни при каких обстоятельствах в любой системе. Я думаю, что все это доказывает, что, неудивительно, что каждая тестируемая операционная система списывает список аргументов хотя бы один раз между временем p1 вызовов execl и временем, когда p2 действительно вызывается. Другими словами, вектор аргумента, созданный вызывающей программой, не используется непосредственно в вызываемой программе и в процессе ее копирования он (опять же неудивительно) "нормализован", что означает, что эффекты любого наложения теряются.

(Я говорю, что это неудивительно, потому что, если вы думаете о том, как действительно работает семейство системных вызовов exec, а также то, как память процессов выкладывается в Unix-подобных системах, нет никакого способа, чтобы список аргументов вызывающей программы можно было использовать напрямую, он должен быть скопирован, по крайней мере, один раз, в адресное пространство нового процесса exec. Кроме того, любой очевидный и простой способ копирования списка аргументов всегда и автоматически будет "нормализовать" его таким образом; ядро должно было бы выполнить значительную, лишнюю, совершенно ненужную работу, чтобы обнаруживать и сохранять любые псевдонимы.)

На всякий случай это важно, я изменил первую программу таким образом:

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

int main()
{
    char test[] = "test";
    char *argv[] = {"p2", test, test, NULL};
    execv("./p2", argv);
}

Результаты не изменились.


Учитывая все это, я согласен с тем, что этот вопрос выглядит как надзор или буглет в стандартах. Я не знаю какой-либо статьи, гарантирующей, что строки, на которые указывает argv, различны, что означает, что параноидально написанная программа, вероятно, не может зависеть от такой гарантии, независимо от того, насколько вероятно, что это (как показывает этот ответ) разумная реализация, вероятно, сделает это именно так.