Programing

배열의 모든 요소를 ​​동일한 숫자로 초기화

crosscheck 2020. 12. 6. 21:13
반응형

배열의 모든 요소를 ​​동일한 숫자로 초기화


얼마 전에 나의 옛 선생님이이 코드를 게시하여 배열을 동일한 숫자 (물론 0이 아닌)로 초기화하는 또 다른 방법이라고 말했습니다.

이 경우 3 개.

그는이 방법이 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);
}

그는 그것이 long4 배 더 길다고 가정합니다 short(보장되지 않습니다. int16_t 및 int64_t를 사용해야 함).

그는 더 긴 메모리 공간 (64 비트)을 가져와 4 개의 짧은 (16 비트) 값으로 채 웁니다. 그는 비트를 16 칸씩 이동하여 값을 설정하고 있습니다.

그런 다음 그는 short 배열을 long 배열로 취급하기를 원하므로 100 대신 25 루프 반복 만 수행하여 100 개의 16 비트 값을 설정할 수 있습니다.

그게 선생님이 생각하는 방식이지만 다른 사람들이 말했듯이이 캐스트는 정의되지 않은 행동입니다.


동일한 값으로 배열을 채우는 방법은 여러 가지가 있으며 성능이 우려되는 경우 측정해야합니다.

C ++에는 배열을 값으로 채우는 전용 함수가 있으며 다음을 사용합니다 ( #include <algorithm>이후 #include <iterator>).

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

최적화 컴파일러가 이와 같은 작업으로 수행 할 수있는 작업을 과소 평가해서는 안됩니다.

컴파일러가하는 일에 관심이 있다면 Matt Godbolt의 Compiler Explorer 는 어셈블러에 대해 약간 배울 준비가되어 있다면 매우 좋은 도구입니다. 여기 에서 볼 수 있듯이 컴파일러는 fill루프가 풀린 상태에서 12 개 (및 비트) 128 비트 저장소에 대한 호출을 최적화 할 수 있습니다 . 컴파일러는 타겟 환경에 대한 지식을 가지고 있기 때문에 소스 코드에서 타겟 별 가정을 인코딩하지 않고도이를 수행 할 수 있습니다.


돼지 세척의 절대적인 부하.

  1. 우선, 컴파일 시간에v 계산됩니다 .

  2. B뒤 따르는 역 참조 동작은 long *B = (long*)A;유형이 관련이 없기 때문에 정의되지 않습니다. B[i]의 역 참조입니다 B.

  3. a long가 a보다 4 배 크다는 가정에 대한 정당성은 없습니다 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기본 유형에 관계없이 잘 정의 된 동작을 갖는를 사용하여 수정되었습니다 .

즉, 코드를 단순하게 유지하고 컴파일러가 대신 마법을 수행하도록하는 것이 가장 좋습니다.


왼쪽 시프트 연산자가 필요한 이유는 무엇입니까?

The point is to fill a bigger integer with multiple copies of the smaller integer. If you write a two-byte value s to a big integer l, then shift the bits left for two bytes (my fixed version should be clearer about where those magic numbers came from) then you'll have an integer with two copies of the bytes that constitute the value s. This is repeated until all pairs of bytes in l are set to those same values. To do the shift, you need the shift operator.

When those values are copied over an array that contains an array of the two-byte integers, a single copy will set the value of multiple objects to the value of the bytes of the larger object. Since each pair of bytes has the same value, so will the smaller integers of the array.

Why I need another array of long?

There are no arrays of long. Only an array of short.


The code your teacher has shown you is an ill-formed program, no diagnostic required, because it violates a requirement that pointers actually point to the thing they claim to be pointed to (otherwise known as "strict aliasing").

As a concrete example, a compiler can analyze your program, notice that A was not directly written to and that no short was written to, and prove that A was never changed once created.

All of that messing around with B can be proven, under the C++ standard, as not being able to modify A in a well formed program.

A for(;;) loop or even a ranged-for is likely to be optimized down to static initialization of A. Your teacher's code, under an optimizing compiler, will optimize to undefined behavior.

If you really need a way to create an array initialized with one value, you could 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 the filled array at compile time, no loops involved.

Using godbolt you can see that the array's value was computed at compile time, and the value 3 was extracted when I access the 50th element.

This is, however, overkill (and ).


I think he is trying to reduce the number of loop iterations by copying multiple array elements at the same time. As other users already mentioned here, this logic would lead to undefined behavior.

If it is all about reducing iterations then with loop-unrolling we can reduce the number of iterations. But it won't be significantly faster for such smaller 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);
}

참고URL : https://stackoverflow.com/questions/46600498/initialize-all-the-elements-of-an-array-to-the-same-number

반응형