Можно ли изменить указатели на строки в argv?

Недавно (январь 2016 г., если вопрос сохраняется достаточно долго) у нас был вопрос 1#comment57931629_35102948">Можно ли изменить строки в argv?.
В разделе комментариев на это ответ, мы (@2501 и я) спорили, действительно ли это строки символов (пример символа **argv), которые можно изменить, или указатели на строки (пример указатель *argv).

Соответствующая стандартная цитата взята из проекта стандарта C11 N1570, §5.1.2.2.1/2:

Параметры argc и argv и строки, на которые указывает массив argv, должны изменяться программой и сохранять свои последние сохраненные значения между запуском программы и завершением программы.

Итак, указатели на строки, на которые указывает argv, изменяемы?


person cadaniluk    schedule 30.01.2016    source источник
comment
Поскольку argv можно изменить, а argv содержит указатели, о которых вы говорите, да.   -  person Simon Shine    schedule 30.01.2016
comment
@SimonShine Но если argv можно изменить, не означает ли это, что можно сделать только argv = ..., а не обязательно *argv = ...?   -  person cadaniluk    schedule 30.01.2016
comment
Поскольку одним из правильных объявлений функции main является int main(int argc, char *argv[]), кажется очевидным, что параметр argv представляет собой массив из char * указателей, а не char const *argv[] указателей. Таким образом, строки должны быть модифицируемыми.   -  person nsilent22    schedule 30.01.2016
comment
@ nsilent22 Как я уже сказал в упомянутом вопросе, в C вы также можете выполнять char* str = "foo";, а *str = 'c'; - это неопределенное поведение. Так что я бы оставил здесь const в стороне.   -  person cadaniluk    schedule 30.01.2016
comment
@SimonShine: argv — это локальная переменная, поэтому ее все равно можно изменить. Но вопрос касается *argv и других указателей.   -  person too honest for this site    schedule 30.01.2016
comment
Хотя сам вопрос хорош, лично я считаю плохим стилем изменять аргументы и *argv. Это может быть полезно для автономной среды, но для этого стандарт не определяет поведение/функции при запуске, поэтому в любом случае это зависит от вашей реализации и среды.   -  person too honest for this site    schedule 30.01.2016
comment
@Olaf Ну, почти все придирки и вопросы о стандарте возникают из-за плохого стиля и крайних случаев, не так ли? И разве стандарт не определяет int main(void) и int main(int, char**) в размещенной реализации?   -  person cadaniluk    schedule 30.01.2016
comment
Когда вы делаете char *str = "foo", вы указываете str на строку char const *, о чем вас предупредят современные компиляторы.   -  person nsilent22    schedule 30.01.2016
comment
@ nsilent22 Я думаю, вы уходите от основной темы. Вы сказали, что тип указателя ясно дает понять, что модификация разрешена. Только тип str делает то же самое, поэтому мой контраргумент. Речь идет о четко определенном и неопределенном, а не о какой-то диагностике, даже не указанной в стандарте C11, насколько мне известно. Кроме того, тип строкового литерала — char[N], если я еще в порядке.   -  person cadaniluk    schedule 30.01.2016
comment
@cad: По сути, именно программист определяет main и несет ответственность за совместимость. В любом случае, принятие этого объявления как данного означает, что *argv на самом деле можно изменить.   -  person too honest for this site    schedule 30.01.2016
comment
Как я уже писал ранее, выполнение char *str = "string"; даст вам предупреждение. Конечно, "обычный" программист мог бы это сделать, но компилятор/библиотека должны соответствовать некоторым стандартам и не передавать в основную функцию аргументы неправильного типа.   -  person nsilent22    schedule 30.01.2016
comment
@nsilent22:; Предупреждение является симптомом. Причина, по которой они предупреждают, заключается в том, что запись в строковый литерал является неопределенным поведением. Вопрос точно спрашивает, является ли запись на *argv также UB. (Обратите внимание, что в стандарте не указано, что строковый литерал — это const char [], а только то, что запись в него — это UB. Что делает литерал только технически const char []).   -  person too honest for this site    schedule 30.01.2016
comment
Я думаю, что это связано: stackoverflow.com/questions/25737434/is -argvn-доступно для записи?rq=1   -  person Giorgi Moniava    schedule 31.01.2016


Ответы (2)


Как указано в вопросе OP, в стандарте C11 прямо указано, что переменные argc и argv, а также строки, на которые указывает массив argv, могут быть изменены. Вопрос в том, поддаются ли эти указатели изменению или нет. Стандарт, похоже, не указывает это явно так или иначе.

Следует отметить два ключевых момента, касающихся формулировок в стандарте:

  1. Если бы указатели предполагались неизменяемыми, стандарт мог бы прояснить это, потребовав, чтобы main объявлялся как int main(int argc, char *const argv[]), как упоминалось в хаках в другом ответе на этот вопрос.

    Тот факт, что нигде в стандарте const не упоминается в связи с argv, кажется преднамеренным. То есть отсутствие const выглядит не опционально, а продиктовано стандартом.

  2. Стандарт последовательно называет argv массивом. Изменение массива относится к изменению его элементов. Таким образом, кажется очевидным, что формулировка в стандарте относится к изменению элементов в массиве argv, когда говорится, что argv можно изменить.

    С другой стороны, массив параметров в языке C (на основе проекта C11 N1570, §6.7.6.3p7) "должен быть изменен на "уточненный указатель на тип"". Таким образом, следующий код,

    int foo(int x[2], int y[2])
    {
        if (x[0] > y[0])
            x = y;
        return x[1];
    }
    

    действителен C11, так как x и y изменены на int *x и int *y соответственно. (Это также повторяется в проекте C11 N1570, §6.3.2.1p3: "... массив... преобразуется в выражение с типом "указатель на тип", который указывает на начальный элемент массива... .".) Очевидно, того же не было бы, если бы x и y были объявлены как локальные или глобальные массивы, а не параметры функции.

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


На практике существует очень давняя традиция изменять указатели в массиве argv. Многие библиотеки имеют функции инициализации, которые принимают указатель на argc и указатель на массив argv, а некоторые из них изменяют указатели в массиве argv (удаляя параметры, характерные для библиотеки); например, GTK+ gtk_init() и MPI_Init() (хотя, по крайней мере, OpenMPI прямо заявляет, что не проверяет или изменить их). Найдите объявление параметра (int *argc, char ***argv); единственная причина для этого - предполагая, что намерение состоит в том, чтобы вызываться из main() с использованием (&argc, &argv) - состоит в том, чтобы изменить указатели, проанализировать и удалить параметры командной строки, специфичные для библиотеки, из параметров командной строки, изменив как argc, так и указатели в argv по мере необходимости.

(Первоначально я заявил, что средство getopt() в POSIX полагается о том, что указатели могут быть изменены — эта функция восходит к 1980 году, принята большинством разновидностей Unix и стандартизирована в POSIX.2 в 1997 году — но это неверно, как указал Джонатан Леффлер в комментарии: POSIX getopt() не изменяет фактические указатели; только GNU getopt() изменяет, и это только тогда, когда переменная окружения POSIXLY_CORRECT не установлена. И GNU getopt_long(), и BSD getopt_long() изменяют указатели, если не установлено POSIXLY_CORRECT, но они намного моложе и менее распространены по сравнению с getopt().)

В мире Unix считалось "переносимым" изменение содержимого строк, на которые указывает массив argv[], и отображение измененных строк в списке процессов. Одним из примеров того, как это было полезно, является пакет DJB daemontools, readproctitle. (Обратите внимание, что строки должны быть изменены на месте и не могут быть расширены, чтобы изменения были видны в списке процессов.)

Все это указывает на очень давнюю традицию, фактически почти с момента рождения С как языка программирования и определенно предшествующую стандартизации С, обработки argc, argv, указателей в массиве argv и содержимого строк, на которые указывает эти указатели, как изменяемые.

Поскольку цель стандарта C состоит не в том, чтобы определить новое поведение, а в том, чтобы систематизировать существующее поведение в различных реализациях (для обеспечения переносимости, надежности и т. д.), можно с уверенностью предположить, что это было непреднамеренным упущением со стороны разработчиков стандарта. явно указать указатели в массиве argv как изменяемые. Все остальное нарушило бы традицию и явно противоречило бы стандарту POSIX (который также предназначен для обеспечения переносимости между системами и расширяет возможности C, не включенные в стандарт ISO C).

person Nominal Animal    schedule 31.01.2016
comment
Стандарт getopt() не зависит от возможности изменения argv; GNU getopt() делает это, потому что он переставляет список аргументов. - person Jonathan Leffler; 31.01.2016
comment
@JonathanLeffler: Чёрт, правда! Я даже проверил некоторые из старых реализаций Unix getopt(), и они тоже сохранили список аргументов нетронутым. Только GNU getopt() изменяет указатель. (Хотя и GNU, и BSD getopt_long() изменяют указатели, даже если они помечены const, если только не установлена ​​переменная среды POSIXLY_CORRECT.) Мне придется исправить свой ответ. - person Nominal Animal; 31.01.2016
comment
Я заменил неправильную часть о getopt(), указанную @JonathanLeffler, ссылками на функции инициализации GTK+ и MPI (и подпись, которую вы можете найти для других функций инициализации библиотеки). Если вы (или кто-либо другой) обнаружите какие-либо другие ошибки, пожалуйста, укажите на них. - person Nominal Animal; 31.01.2016
comment
Аргумент const точки #1 опровергается тем, что const было добавлено к C намного позже main(). Изменение подписи main() путем добавления const нарушит существующий код. Улучшение № 2 само по себе потенциально достаточно. - person chux - Reinstate Monica; 07.04.2017

Является ли указатель модифицируемым или нет, зависит от постоянства указателя. Параметр argv объявляется как char *argv[] или char **argv. Это зависит от среды, рассматривают ли они это как char *const argv[] или нет (я ничего не знаю).

person haccks    schedule 30.01.2016
comment
Но можете ли вы действительно делать заявления о том, что указатели доступны для записи, просто зная, что они не const? См. мой комментарий к этому вопросу. . - person cadaniluk; 31.01.2016
comment
@кад; Вы задали вопрос об изменении указателя, а не содержимого, на которое он указывает: Могут ли быть изменены указатели на строки в argv?. char const *a и char *const a имеют разные значения. - person haccks; 31.01.2016
comment
Но тогда что говорят мне первые два предложения вашего ответа? В основном они говорят мне, что указатели на строки изменяемы, потому что они объявлены соответствующим образом. Итак, вы говорите, что их можно модифицировать в зависимости от реализации? - person cadaniluk; 31.01.2016
comment
@кад; Если вы объявите char * p = "abcd", вы не сможете выполнить p[0] = 'e', но сможете изменить сам указатель p. p = "Hello". Но если в какой-то среде char * p = "abcd" интерпретируется как char * const p, то изменить p невозможно. - person haccks; 31.01.2016