При копировании мусора

#c #arrays #standards #c11

#c #массивы #стандарты #c11

Вопрос:

Пару дней назад у меня была небольшая дискуссия здесь о копировании, давайте назовем это мусором, потому что это то, что есть на самом деле, из одного массива в другой, и если это приемлемо в стандарте C (ISO / IEC 9899-2011) или нет.

Завернутый в какой-нибудь пример-код для ясности:

 #include <stdio.h>
#include <stdlib.h>

#define ARRAY_SIZE 10

/*
    ?  = any byte with ('?' != '?' || '?' == '?'), that is: '?' may or may not be equal to '?'.
         tl;dr: just random garbage
   'x' = a one byte large (not necessary ASCII-encoded) known and defined character.
         tl;dr: neither random nor garbage
*/

int main(){
  // array = [?,?,?,?,?,?,?,?,?,?]
  char array[ARRAY_SIZE];
  // copy =  [?,?,?,?,?,?,?,?,?,?]
  char copy[ARRAY_SIZE];
  int i;

  // fill a part of "array" with a NUL terminated string
  // "part" is not necessary half 1 of it, the only condition is: #part < ARRAY_SIZE
  // such that we have at least one byte of garbage
  for(i = 0;i < ARRAY_SIZE/2;i  ){
    // casting "i" is a bit "holier than the pope", admitted
    array[i] = (char)i   '0';
  }
  array[i] = '';
  // array = ['0','1','2','3','4','',?,?,?,?]

  // "use" the array "array"
  printf("array = %sn",array);

  // copy all of the elements of "array" to "copy" including
  // the garbage at the end
  for(i = 0;i < ARRAY_SIZE;i  ){
    copy[i] = array[i];
  }
  // copy = ['0','1','2','3','4','',?,?,?,?]

  // "use" the array "copy"
  printf("copy =  %sn",copy);

  // no further use of either "array" or "copy".
  // obvious at the end of main() but meant generally, of course
  exit(EXIT_SUCCESS);
}
  

Параграф в стандарте, который определяет эти дочерние массивы, находится в списке производных типов:

6.2.5 Типы

20 Любое количество производных типов может быть создано из типов объектов и функций следующим образом:

  • Тип массива описывает непрерывно распределенный непустой набор объектов с определенным типом объекта-члена, называемым типом элемента. Тип элемента должен быть полным всякий раз, когда указан тип массива. Типы массивов характеризуются типом их элемента и количеством элементов в массиве. Говорят, что тип массива является производным от типа его элемента, и если тип его элемента равен T, тип массива иногда называют «массивом из T«. Построение типа массива из типа элемента называется «выводом типа массива».

Мой вопрос: означает ли «непустое множество» просто, что n (где n обозначает целочисленный литерал; это не имеет ничего общего с VLAs) в объявлении T a[n] должно быть больше нуля?

Для тех из вас, кому нужна практическая причина: в области реального времени четко определенное количество операций предпочтительнее неизвестного количества. Он также работает медленнее большую часть времени (при условии случайного распределения входных данных) из-за необходимого измерения, и эти накладные расходы имеют большое значение во встроенной области. Каждый сэкономленный наноамперный час считается, когда батарее нужно сохранить достаточно энергии, чтобы работать в течение нескольких лет.

Комментарии:

1. Чтение неинициализированных данных всегда является неопределенным поведением. Если цикл копирования не считывал неинициализированные данные (т. Е. Он проверял терминатор или длину и останавливался на этом), тогда ваша программа была бы четко определена.

2. ДА. Это просто стандартно. Вы не можете объявить буфер размером 0, и стандарт описывает это как «массив». Однако вы можете передать N = 0 и указатель без каких-либо проблем, поэтому, конечно, мы можем иметь пустые массивы, используя этот термин в его собственном смысле. malloc(0) также немного рискованно.

3. Технически Иоахим прав. Однако ваша встроенная система не будет иметь представлений ловушек, и вы можете уверенно копировать мусор как типы, отличные от символов, с ожидаемым эффектом.

4. Раньше в ассемблере была директива: ничего не предполагать . С другой стороны, ассемблер был прав. Неинициализированный == неинициализированный. Разберитесь с этим.

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

Ответ №1:

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

Ответ №2:

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

Требование, чтобы код не мог копировать элемент массива, если он не инициализировал его, даже если тип элемента не будет иметь представления ловушек и даже если значение копии не будет иметь значения, снизит эффективность, с которой могут быть выражены многие алгоритмы. С другой стороны, учитывая что-то вроде:

 struct fnord { unsigned char q; ... }
struct fnord x=foo[i];
doSomething(x.q);
...
doSomething(x.q);
  

неясно, потребуется ли компилятору гарантировать, что одно и то же
значение будет передано обоим вызовам функции в случаях, когда foo[i] содержится неопределенное значение. Структурам не разрешается иметь представления ловушек (члены типов, отличных от unsigned char might, но копирование всей структуры является определенным поведением, даже если хранилище, лежащее в основе некоторых членов, содержит представление ловушек их типа). С другой стороны, запись в x может рассматриваться как причина x сохранения неопределенного значения, так что вызовы doSomething могут получать разные значения.

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

Комментарии:

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

2. @deamentiaemundi: Язык C, с практической точки зрения, достаточно фрагментирован, что единственное решение, которое я вижу, — это иметь стандартные режимы распознавания, которые разрешали бы или запрещали определенные формы оптимизации; при наличии #pragma одного режима определенные конструкции, которые могут быть или не быть определены в C11были бы однозначно определены; в другом режиме они явно считались бы неопределенными. Поведение в отсутствие любой директивы будет зависеть от реализации. В отсутствие каких-либо таких директив…

3. …программисты должны блокировать любые изменения в стандарте, которые будут рассматривать как UB конструкции, которые при их чтении Стандарта были определены (и полезны), а авторы компиляторов, скорее всего, заблокируют любые изменения, которые прояснят, что конструкции, которые при их чтении стандарта были UB (и, следовательно, не должныдолжно поддерживаться) должно было рассматриваться как имеющее определенное поведение даже за счет оптимизации блокировки. Распознавание нескольких различных режимов компиляции позволит избежать необходимости определять, определены или не определены такие конструкции в отсутствие директивы.

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

5. @deamentiaemundi: Учитывая послужной список Комитета, я думаю, было бы более вероятно, что какая-то другая организация сформулирует полезный стандарт, чем то, что это сделает Комитет, но я не знаю, как побудить кого-либо с достаточным влиянием создать полезный стандарт, чтобы приложить усилия. Кроме того, — если части, которые, я думаю, большинство людей ни в малейшей степени не сочтут двусмысленными, интерпретируются авторами компиляторов вопреки их ясному смыслу, как вы думаете, Комитет собирается изменить стандарт таким образом, который указывает, что поведение компиляторов никогда не было законным?