Initialize all array elements to the same number

Some time ago, my old teacher published this code, saying that this is another way to initialize an array with the same number (except for zero, of course).

In this case, three.

He said this path is slightly better than the for loop. Why do I need a left shift operator? Why do I need another array? I do not understand what is happening here.

 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); } 
+58
c ++ arrays for-loop
Oct 06 '17 at 7:29 on
source share
7 answers

It assumes that long is four times longer than short (this is not guaranteed, it should use int16_t and int64_t).

It occupies a longer memory space (64 bits) and fills it with four short (16 bits) values. It sets values โ€‹โ€‹by shifting bits by 16 spaces.

Then he wants to treat the array of shorts as an array of lengths, so he can set 100 16-bit values โ€‹โ€‹by doing only 25 iteration cycles instead of 100.

The way your teacher thinks, but as others have said, this cast is undefined.

+68
Oct 06 '17 at 7:48
source share

There are many ways to populate an array with the same value, and if you are concerned about performance, you need to measure.

C ++ has a special function to populate an array with a value, and I would use this (after #include <algorithm> and #include <iterator> ):

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

You should not underestimate what optimizing compilers can do with something like this.

If you are interested in knowing what the compiler does, then Matt Godbolt Explorer compiler is a very good tool if you are ready to learn a little assembler. As you can see from here , compilers can optimize the fill call for twelve (and bits) of 128-bit storages with any deployed loops. Since compilers are aware of the target environment, they can do this without encoding any target assumptions in the source code.

+77
Oct 06 '17 at 7:51 on
source share

What an absolute load on hogwash.

  • For starters, v will be computed at compile time.

  • Dereferencing behavior of B after long *B = (long*)A; equals undefined since the types are not bound. B[i] is an dereferencing of B

  • There is no excuse for suggesting that a long is four times that of a short .

Use the for loop in a simple way and trust the compiler optimizer. Pretty please with sugar on top.

+43
Oct 06 '17 at 7:31 on
source share

The question has a C ++ tag (no C tag), so this should be done in C ++ style:

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

In addition, the teacher is trying to outwit the compiler, which can do mental things. There is no point in doing such tricks, since the compiler can do this for you if it is configured correctly:

As you can see, the result code -O2 (almost) the same for each version. In the case of -O1 tricks give some improvement.

So, in the bottom line you should make a choice:

  • Enter hard code to read and do not use compiler optimization.
  • Enter readable code and use -O2

Use the Godbolt website to experiment with other compilers and configurations. See also the last cppCon session .

+21
06 Oct '17 at 9:16 on
source share

As explained by other answers, the code violates the rules of type aliases and makes assumptions that are not guaranteed by the standard.

If you really want to do this optimization manually, this will be the right way, which has well-defined behavior:

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

Insecure assumptions about the size of objects were corrected with sizeof , and anti-aliasing was corrected with std::memcpy , which has well-defined behavior regardless of the underlying type.

However, it is best to keep your code simple and let the compiler do its magic instead.




Why do I need a left shift operator?

The point is to populate a larger integer with multiple copies of a smaller integer. If you write a two-byte value of s to a large integer l , then shift the bits left for two bytes (my fixed version should be clearer where these magic numbers come from), then you will have an integer with two copies of the bytes that make up the value s . This is repeated until all pairs of bytes in l are set to the same values. To perform a shift, you will need a shift operator.

When these values โ€‹โ€‹are copied over an array containing an array of double-byte integers, one copy sets the value of several objects to the bytes of the larger object. Since each pair of bytes has the same value, then the smaller integers of the array.

Why do I need another long array?

No long arrays. Only the array is short .

+8
06 Oct '17 at 9:06 on
source share

The code that your teacher showed that you are a poorly formed program does not require diagnostics because it violates the requirement that the pointers actually point to what they state that they are pointing to (otherwise known as a "strict alias").

As a concrete example, the compiler can analyze your program, noting that A not written directly and that no short was written, and prove that A never changed after creation.

All this with B can be proved in accordance with the C ++ standard, since it cannot change A in a well-formed program.

A for(;;) loop or even ranking is likely to be optimized before A is statically initialized. Your teacher code under an optimizing compiler will be optimized for undefined behavior.

If you really need a way to create an array initialized with a single value, you can use this:

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

and now:

 int main() { auto A = make_filled_array<short, 100, 3>(); std::cout << "\n"; print(A.data(),100); } 

creates a populated array at compile time, unused loops.

Using godbolt , you can see that the value of the array was calculated at compile time, and the value 3 was retrieved when I access the 50th element.

This, however, is overkill (and C ++ 14 ).

+7
Oct 06 '17 at 14:23
source share

I think it is trying to reduce the number of iterations of the loop while copying several elements of the array at the same time. Like the other users mentioned here, this logic will lead to undefined behavior.

If we are talking about reducing iterations, then when the loop unfolds, we can reduce the number of iterations. But it will not be much faster for such small arrays.

 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); } 
+3
Oct 06 '17 at 8:50
source share



All Articles