Разыменование указателя на статические данные вне области действия в C - PullRequest
13 голосов
/ 03 апреля 2012

Отказ от ответственности: Этот вопрос носит чисто академический характер. Пример, который я собираюсь привести, вероятно, плохой стиль.

Предположим, что в C я пишу подпрограмму этого вида:

char *foo(int x)
{
    static char bar[9];

    if(x == 0)
        strcpy(bar, "zero");
    else
        strcpy(bar, "not zero"),

    return bar;
}

Затем в другом месте я использую foo следующим образом:

printf("%i is %s\n", 5, foo(5));

Моя ментальная модель указателей и статических переменных предсказывает, что на практике вывод этого printf будет

5 не ноль

... но действительно ли это требуется стандартом C, или я нахожусь на территории носовых демонов?

Что еще хуже, что-то вроде

strcpy(foo(5), "five");

Моя ментальная модель говорит, что это должно "работать", если это явно не незаконно, хотя это несколько бессмысленно, поскольку это не влияет на вывод foo. Но опять же, это на самом деле определяется стандартом?

Ответы [ 5 ]

12 голосов
/ 03 апреля 2012

То, что вы написали, в порядке; нет носовых демонов, ожидающих вас. Даже strcpy() пример «ОК», потому что вы не попираете границы массива. Это «ОК» в кавычках, потому что это не очень хорошая идея, но, как написано, нет доступа к памяти за пределами границ и, следовательно, нет неопределенного поведения. Данные static в функции присутствуют на протяжении всего жизненного цикла программы и содержат последнее записанное в нее значение.

Могут возникнуть проблемы, если вы попытаетесь:

printf("%i is %s but %i is %s\n", 5, foo(5), 0, foo(0));

Вы получите неправильный ответ для одного из двух чисел, но он не определен, который будет неправильным ответом.

5 голосов
/ 03 апреля 2012

Ну, так как вы хотите получить цитату из стандарта:

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

Объект, идентификатор которого объявлен без спецификатора класса хранения _Thread_local и с внешней или внутренней связью или со статическим указателем класса хранения, имеет статическую длительность хранения. Его время жизни - это полное выполнение программы , и его сохраненное значение инициализируется только один раз, до запуска программы.

2 голосов
/ 03 апреля 2012

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

Логика вашего кода менее хороша;например, функция не является входной и не поточнобезопасной, и, конечно же, неохраняемая строковая операция является прямым самоубийством.

1 голос
/ 03 апреля 2012

Я не вижу ничего плохого в этом коде.

Указатель, который возвращает foo(), является действительным указателем, и вы можете разыменовать его.

Редактировать: Под "ничего плохого" я подразумеваю, что все синтаксически нормально, но я согласен с другими ответами, что этот код не подходит ни в каком значении этого слова по разным причинам. Это просто правильно в соответствии со стандартом C.

0 голосов
/ 04 апреля 2012

Ничего плохого в этом нет. Лучше стиль будет

 const char *foo(int x);

Тогда вы не можете войти в него, не выбрасывая константу. Если вы действительно хотите сломать это, ничто не может остановить вас.

, если вы хотите сделать это вновь.

const char *foo(int x)
{
 return (x? "not zero" : "zero");
}
...