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


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

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

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

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);
}
7 58

7 ответов:

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

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

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

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

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

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

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

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

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

какой абсолютный груз фигни.

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

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

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

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

вопрос имеет тег C++ (без тега C), поэтому это должно быть сделано в стиле 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.

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

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

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, потом shift биты, оставшиеся на два байта (моя фиксированная версия должна быть яснее, где эти магические числа пришли из) тогда у вас будет целое число с двумя копиями байтов, которые составляют значение s. Это повторяется до тех пор, пока все пары байтов в l задаются те же значения. Чтобы сделать сдвиг, вам нужен оператор сдвига.

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

зачем мне нужен еще один массив long?

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

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

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

все это Мессинг примерно с B может быть доказано, в соответствии со стандартом C++, как не в состоянии изменить A в хорошо сформированной программы.

A for(;;) loop или даже ranged-for, вероятно, будут оптимизированы вплоть до статической инициализации A. Код вашего учителя под оптимизирующим компилятором будет оптимизироваться до неопределенного поведения.

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

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-му элементу.

это, однако, перебор (и c++14).

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

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

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);
}