Инициализировать все элементы массива на один и тот же номер

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

В этом случае три.

Он сказал, что этот путь немного лучше, чем цикл for. Зачем мне нужен оператор сдвига влево? Зачем мне нужен еще один массив? Я ничего не понимаю, что происходит здесь.

int main() {

    short int A[100];

    long int v = 3;
    v = (v << 16) + 3;
    v = (v << 16) + 3;
    v = (v << 16) + 3;
    long *B = (long*)A;

    for(int i=0; i<25; i++)
        B[i] = v;

    cout << endl;
    print(A,100);
}

Ответ 1

Он предполагает, что long в четыре раза длиннее short (это не гарантируется, он должен использовать int16_t и int64_t).

Он занимает более длинное пространство памяти (64 бит) и заполняет его четырьмя короткими (16 бит) значениями. Он устанавливает значения, сдвигая биты на 16 пробелов.

Затем он хочет обработать массив шорт как массив длин, поэтому он может настроить 100 16-битных значений, выполнив только 25 циклов итераций вместо 100.

То, как думает ваш учитель, но, как говорили другие, это приведение undefined.

Ответ 2

Существует множество способов заполнить массив с тем же значением, и если вас беспокоит производительность, вам нужно измерить.

С++ имеет специальную функцию для заполнения массива со значением, и я бы использовал это (после #include <algorithm> и #include <iterator>):

std::fill(std::begin(A), std::end(A), 3);

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

Если вам интересно узнать, что делает компилятор, то Matt Godbolt Компилятор Explorer - очень хороший инструмент, если вы готовы учиться немного ассемблера. Как вы можете видеть из здесь, компиляторы могут оптимизировать вызов fill для двенадцати (и бит) 128-разрядных хранилищ с любыми развернутыми циклами. Поскольку компиляторы знают о целевой среде, они могут сделать это без кодирования каких-либо целевых допущений в исходном коде.

Ответ 3

Какая абсолютная нагрузка на hogwash.

  • Для начала v будет вычислен во время компиляции.

  • Поведение разыменования B после long *B = (long*)A; равно undefined, поскольку типы не связаны. B[i] является разыменованием B.

  • Нет никакого оправдания для предположения, что a long в четыре раза больше, чем a short.

Используйте цикл for простым способом и доверяйте оптимизатору компилятора. Довольно пожалуйста, с сахаром на вершине.

Ответ 4

Вопрос имеет тег С++ (нет тега C), поэтому это должно быть сделано в стиле С++:

// C++ 03
std::vector<int> tab(100, 3);

// C++ 11
auto tab = std::vector<int>(100, 3);
auto tab2 = std::array<int, 100>{};
tab2.fill(3);

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

Как вы можете видеть, код результата -O2 (почти) одинаковый для каждой версии. В случае -O1 трюки дают некоторое улучшение.

Итак, в нижней строке вы должны сделать выбор:

  • Введите жесткий код для чтения и не используйте оптимизацию компилятора.
  • Введите читаемый код и используйте -O2

Используйте сайт Godbolt для экспериментов с другими компиляторами и конфигурациями. См. Также последний сеанс cppCon.

Ответ 5

Как объясняется другими ответами, код нарушает правила псевдонимов типов и делает допущения, которые не гарантируются стандартом.

Если вы действительно хотите сделать эту оптимизацию вручную, это будет правильный способ, который имеет четко определенное поведение:

long v;
for(int i=0; i < sizeof v / sizeof *A; i++) {
    v = (v << sizeof *A * CHAR_BIT) + 3;
}

for(int i=0; i < sizeof A / sizeof v; i++) {
    std:memcpy(A + i * sizeof v, &v, sizeof v);
}

Небезопасные предположения о размерах объектов были исправлены с помощью sizeof, а нарушение сглаживания было исправлено с помощью std::memcpy, который имеет четко определенное поведение независимо от базового типа.

Тем не менее, лучше всего держать ваш код простым и позволить компилятору сделать свою магию вместо этого.


Зачем мне нужен оператор сдвига влево?

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

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

Зачем мне нужен другой массив long?

Нет массивов long. Только массив short.

Ответ 6

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

В качестве конкретного примера компилятор может проанализировать вашу программу, заметив, что A не был написан напрямую и что no short был написан, и докажите, что A никогда не менялся после создания.

Все это с помощью B может быть доказано в соответствии со стандартом С++, поскольку оно не может изменить A в хорошо сформированной программе.

A for(;;) цикл или даже ранжирование, вероятно, будет оптимизировано до статической инициализации A. Ваш код преподавателя под оптимизирующим компилятором будет оптимизирован для поведения undefined.

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

template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>) {
  return [](auto&&f)->decltype(auto) {
    return f( std::integral_constant<std::size_t, Is>{}... );
  };
}
template<std::size_t N>
auto index_upto(std::integral_constant<std::size_t, N> ={})
{
  return index_over( std::make_index_sequence<N>{} );
}
template<class T, std::size_t N, T value>
std::array<T, N> make_filled_array() {
  return index_upto<N>()( [](auto...Is)->std::array<T,N>{
    return {{ (void(Is),value)... }};
  });
}

и теперь:

int main() {

  auto A = make_filled_array<short, 100, 3>();

  std::cout << "\n";
  print(A.data(),100);
}

создает заполненный массив во время компиляции, не задействованные петли.

Используя godbolt, вы можете видеть, что значение массива вычислялось во время компиляции, а значение 3 было извлечено, когда я получаю доступ к 50-му элемент.

Это, однако, overkill (и ).

Ответ 7

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

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

int main() {

    short int A[100];

    for(int i=0; i<100; i+=4)
    {
        A[i] = 3;
        A[i + 1] = 3;
        A[i + 2] = 3;
        A[i + 3] = 3;
    }
    print(A, 100);
}