Хорошо ли определен доступ к частично назначенному массиву за назначенной частью?

Скажем, у меня есть этот код:

void foo() {
  char s[10];
  char v1 = s[0]; // UB
  char v2 = s[10]; // also UB
}

void bar() {
  char s[10];
  strcpy(s, "foo");
  char v3 = s[3]; // v3 is zero
  char v4 = s[0]; // v4 is 'f'
  char v5 = s[4]; // What?
}

Поскольку доступ к адресам от s[0] до s[3] осуществляется в strcpy, и что s [0] до s [9] находятся в непрерывной памяти, я полагаю, что весь массив должен содержать некоторое значение (включая неопределенное).

Четко ли определена операция о v5? Или v5 - это только неопределенное значение (без отключения какого-либо UB)?

Что делать, если массив имеет тип int и все еще частично назначен?


person iBug    schedule 04.05.2018    source источник
comment
s[4] никогда не был назначен, поэтому мы находимся в той же ситуации, что и в строке char v1 = s[0]; // UB. strcpy назначает здесь только s[0] s[3].   -  person Jabberwocky    schedule 04.05.2018
comment
Я предполагаю, что это хорошо определено, что s5 / s [4] будет содержать значение от -128 до 127, тогда как точное значение, которое он будет содержать, не определено.   -  person Pras    schedule 04.05.2018
comment
@Stargateur Нет, совершенно не обман. Этот вопрос касается , ли UB доступ к массиву , который был занят адресом.   -  person iBug    schedule 04.05.2018
comment
TL; DR: поскольку char гарантирует отсутствие значения представления ловушки (если я правильно помню), значение просто неопределенное. Итак, хорошо определенное - это слишком много, я скажу, что оно определено реализацией и, как правило, оно будет просто содержать значение мусора.   -  person Stargateur    schedule 04.05.2018
comment
@iBug, и это приведет к повторению того, что говорит дурак ... извините, но я, как эксперт, говорю вам, что это та же проблема.   -  person Stargateur    schedule 04.05.2018
comment
@Stargateur IIRC, единственный тип, гарантированно не имеющий значения прерывания, - это unsigned char, но не его подписанная версия или версия с неизвестной подписью.   -  person iBug    schedule 04.05.2018
comment
@iBug Если вы правы, просто измените то, что поведение реализовано определенным. Что бы вы ни выбрали, посмотрите, имеет ли ваш тип представление ловушки (и что бы этот вопрос ни выглядел бесполезным, потому что просто не используйте инициализированное значение;))   -  person Stargateur    schedule 04.05.2018
comment
@Lanting: вопросы C не дублируют вопросы C ++. Правила различны для разных языков, и ответы должны быть конкретными для каждого языка, за исключением случаев, когда конкретно спрашивают о чем-то общем для обоих языков.   -  person Eric Postpischil    schedule 04.05.2018
comment
@Stargateur: я считаю, что типы символов могут иметь представления ловушек, кроме unsigned char. Однако параграф C 2011, в котором говорится, что чтение объекта, имеющего представление прерывания, является неопределенным поведением, 6.2.6.1 5, в частности, исключает символьные типы. Таким образом, чтение символа, имеющего представление прерывания, не считается неопределенным.   -  person Eric Postpischil    schedule 04.05.2018
comment
@iBug: (a) Типы символов обрабатываются по-разному в стандарте C. Ответ на этот вопрос был бы другим, если бы вы использовали массив int. (б) Ваша посылка ошибочна. char v1 = s[0]; не имеет неопределенного поведения.   -  person Eric Postpischil    schedule 04.05.2018
comment
@EricPostpischil Значит, вы имеете в виду, что ни одна из vx переменных не является UB. Все либо четко определено, либо содержит неопределенные значения?   -  person iBug    schedule 04.05.2018
comment
@EricPostpischil На самом деле, я не уверен, поскольку адрес s никогда не берется, s[0] - это UB в foo(), я думаю.   -  person Stargateur    schedule 04.05.2018
comment
Не определен доступ к неинициализированному объекту, если он мог быть объявлен с классом хранения register. Вопрос в том, как это преобразовать в массивы? Кажется, что массивы объявляются с помощью register, но в приложении J2 говорится, что UB должен преобразовывать классифицированный регистром массив в его первый член, но такое преобразование определяется тем, как определяется индексирование, поэтому кажется, что массивы, объявленные регистром, не индексируются, но поскольку вы индексируете, массив не может быть объявлен регистром, поэтому разыменования должны давать неопределенные значения, но не приводить к UB. Хотя это странно.   -  person PSkocik    schedule 04.05.2018
comment
@Stargateur: s[0] определяется стандартом C как (*)((s)+(0)). (s)+(0) - это адрес s[0]. У него есть адрес, поэтому не может быть register.   -  person Eric Postpischil    schedule 04.05.2018
comment
@iBug: Да, неинициализированные элементы имеют неопределенные значения. А для символьных типов в 6.2.6.1 5 их чтение не считается неопределенным поведением, даже если они содержат представления ловушек.   -  person Eric Postpischil    schedule 04.05.2018
comment
@EricPostpischil Да, это то, через что я прошел в первую очередь, но s не является адресом s aka _3 _... Это сам массив, который вы должны взять адрес, а не один из его адресов значений;) но что бы PSkocik ни дал прекрасное объяснение, что это не UB, потому что у массива не может быть квалификатора регистра.   -  person Stargateur    schedule 04.05.2018
comment
@Stargateur: В сторону: как я читал стандарт, массив может иметь квалификатор регистров, если вы никогда не используете массив! Все, что позволяет преобразовать его в указатель на его первый элемент, не определено. Но вы можете применить к нему sizeof.   -  person Eric Postpischil    schedule 04.05.2018
comment
@EricPostpischil Я тоже это читаю. Интересно, зачем тогда вообще разрешать класс хранения регистров. Я не могу придумать для этого варианта использования.   -  person PSkocik    schedule 04.05.2018
comment
@ Мичи s[] = { 'f', 'o, 'o', '\0' };. strcpy копирует терминатор NUL. Об этом четко сказано в документации.   -  person Jabberwocky    schedule 04.05.2018
comment
@Michael Walz Я удалил свой комментарий, потому что неправильно понял ваш, вот и все :)). Я знаю, что делает strcpy.   -  person Michi    schedule 04.05.2018
comment
@PSkocik: Я не думаю, что авторы Стандарта хотели запретить реализациям, которые могут с пользой хранить небольшой массив в регистре (например, с использованием комбинации сдвигов переменных и / или инструкций вставки / извлечения битовых полей), позволяя программистам использовать register ключевое слово для запроса такого хранилища. С другой стороны, они также не хотели требовать, чтобы реализации принимали такой код. Стандартный способ описания функций, которые реализации могут поддерживать, но не обязательны, - это классифицировать эти функции как неопределенное поведение.   -  person supercat    schedule 05.05.2018
comment
Что вы имеете в виду под словом "доступ в strcmp"? В этом коде нет strcmp   -  person M.M    schedule 05.05.2018
comment
@supercat Спасибо. Я всегда забываю об этих определенных реализацией ситуациях UB с хорошим поведением.   -  person PSkocik    schedule 05.05.2018
comment
@PSkocik: Проблема, по сути, в том, что авторы Стандарта никогда не считали необходимым санкционировать то, что в то время казалось здравым смыслом. Если реализация нацелена на необычную платформу, где хранилище, которое никогда не было написано, иногда ведет себя странно, не следует ожидать, что реализация защитит его от этого. Однако это не означает, что реализации, не нацеленные на такие платформы, должны изо всех сил стараться использовать разрешение Стандарта на то же самое.   -  person supercat    schedule 05.05.2018


Ответы (3)


Не может быть неопределенным, потому что там char может быть представление ловушки, потому что 6.2.6.1p5 говорит, что доступ ко всему с символьным типом хорошо определен.

Это могло быть неопределенным из-за 6.3.2.1p2

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

Итак, вопрос в том, мог ли массив быть объявлен с классом хранения регистров?

Ответ на этот вопрос - нет, не могло быть, потому что вы индексируете его. Индексирование определяется в соответствии с 6.5.2.1p2

(

Постфиксное выражение, за которым следует выражение в квадратных скобках [], является индексированным обозначением элемента объекта массива. Определение оператора индекса [] заключается в том, что E1 [E2] идентично (* ((E1) + (E2))). Из-за правил преобразования, которые применяются к бинарному оператору +, если E1 является объектом массива (эквивалентно указателем на начальный элемент объекта массива), а E2 является целым числом, E1 [E2] обозначает E2-й элемент E1 (отсчет с нуля). )

с точки зрения покрытия массива до адреса его первого элемента, но для массива, классифицированного по регистрам, такое преобразование не было бы определено согласно пункту маркированного списка:

Значение lvalue, имеющее тип массива, преобразуется в указатель на начальный элемент массива, а объект массива имеет класс хранения регистров (6.3.2.1).

в приложении J.2 Неопределенное поведение, что означает массив не мог быть объявлен register.

Сноска 121 в 6.7.1 Спецификаторы класса хранения уточняют это:

адрес любой части объекта, объявленного с помощью регистра спецификатора класса хранения, не может быть вычислен ни явно (с использованием унарного оператора &, как описано в 6.5.3.2), ни неявно (путем преобразования имени массива в указатель, как описано в 6.3.2.1). Таким образом, единственные операторы, которые могут быть применены к массиву, объявленному с регистром спецификатора класса хранения, - это sizeof и _Alignof.

(Другими словами, хотя язык допускает регистровые массивы, они по существу непригодны для использования).

Следовательно, код вроде:

char unspecified(void){ char s[1]; return s[0]; }

вернет неопределенное значение, но не сделает поведение вашей программы неопределенным.

person PSkocik    schedule 04.05.2018
comment
Это вообще не решает проблему strcpy ... OP, похоже, думает, что может быть разница между v[0] в первом случае и байтами массива после конца строки во втором случае, поэтому вам следует выяснить, идентичны ли эти две ситуации на самом деле - person M.M; 05.05.2018

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

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

Предполагая, что «unsigned char» составляет 8 бит, а «unsigned» - 24 или более, какие значения могут возвращать следующие значения:

unsigned test1(unsigned char *p)
{
  unsigned x=p[0];
  unsigned y=p[0];
  unsigned z=y;
  return x | (y << 8) | (z << 16);
}
unsigned test(void)
{
  unsigned char foo[1];
  return test1(foo); // Note that this takes the address of 'foo'.
}

Лично я сомневаюсь, что будет какой-либо реальный недостаток в том, чтобы требовать, чтобы код, сгенерированный для test1, вел себя так, как если бы x, y и z все имели одно и то же значение в диапазоне 0..255, или - как минимум - вели себя так, как будто y и z имеют одинаковое значение. Я не думаю, что авторы Стандарта ожидали, что какая-либо непонятная реализация не будет вести себя подобным образом, но Стандарт на самом деле этого не требует, и некоторые люди, кажется, считают, что такое поведение будет чрезмерно ограничивать оптимизацию. .

person supercat    schedule 04.05.2018

Да, это неопределенное поведение.

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

person Lie Ryan    schedule 04.05.2018
comment
Чтение неинициализированных элементов массивов приводит к неопределенным значениям, а не к неопределенному поведению. - person Eric Postpischil; 04.05.2018