将数组的所有元素初始化为相同的数字

Cim*_*ive 59 c++ arrays for-loop

前段时间,我的老教师发布了这段代码,说这是将数组初始化为相同数字的另一种方法(当然不是零).

在这种情况下三个.

他说这种方式比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);
}
Run Code Online (Sandbox Code Playgroud)

CB *_*ley 75

有很多方法可以用相同的值填充数组,如果你关心性能,那么你需要测量.

C++有一个专用的函数来填充一个值的数组,我会使用它(之后#include <algorithm>#include <iterator>):

std::fill(std::begin(A), std::end(A), 3);
Run Code Online (Sandbox Code Playgroud)

你不应该低估优化编译器可以用这样的东西做什么.

如果您有兴趣了解编译器的功能,那么Matt Godbolt的Compiler Explorer是一个非常好的工具,如果您准备学习一点汇编程序的话.从这里可以看出,编译器可以优化fill对12个(和一些)128位存储的调用,并且可以展开任何循环.因为编译器具有目标环境的知识,所以他们可以在不编码源代码中的任何特定于目标的假设的情况下执行此操作.

  • 为什么你在Godbolt的例子中制作数组`extern`?这导致GCC在"A"没有正确对齐的情况下做了很多不相关的事情,这使得你的观点不是很明显.为什么不简单地删除`extern`关键字? (8认同)

小智 67

他认为这long是四倍short(不保证;他应该使用int16_t和int64_t).

他占用了更长的内存空间(64位)并用四个短(16位)值填充它.他通过将位移16个空格来设置值.

然后他想将一组shorts视为long数组,因此他只需要25次循环迭代而不是100次就可以设置100个16位值.

这就是你的老师的想法,但正如其他人所说,这种演员是不明确的行为.

  • 从技术上讲,演员阵容本身就很好.它是通过具有UB的错误类型的指针访问的. (24认同)
  • @LeeDanielCrocker当然,你可以考虑任何你想要的东西.但规范是规范,编译器经常做一些你可能会认为在未定义的行为时会感到惊讶或破坏的事情. (20认同)
  • @LeeDanielCrocker我很确定GCC会根据严格的别名规则做出假设,这些规则会在出现此代码时中断.实际上,几乎所有对优化有任何自命不凡的编译器都会. (4认同)

Bat*_*eba 44

什么是hogwash的绝对负荷.

  1. 对于初学者,v将在编译时计算.

  2. 取消引用B后续行为long *B = (long*)A;未定义,因为类型不相关.B[i]是一个解除引用B.

  3. 假设a long是a 的四倍,没有任何理由short.

for以简单的方式使用循环并信任编译器进行优化.非常好,上面加糖.

  • 从技术上讲,它不是构建B的转换,它是未定义的行为,它是通过它的访问. (4认同)

Mar*_*k R 20

这个问题有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);
Run Code Online (Sandbox Code Playgroud)

此外,老师正在尝试智能的编译器,它可以做令人兴奋的事情.没有必要做这样的技巧,因为如果配置正确,编译器可以为你做这些:

如您所见,-O2每个版本的结果代码(几乎)相同.在这种情况下-O1,技巧会有所改善.

所以最重要的是,你必须做出选择:

  • 编写难以阅读的代码,不要使用编译器优化
  • 编写可读代码并使用 -O2

使用Godbolt网站试验其他编译器和配置.另请参阅最新的cppCon演讲.

  • +1代表*所以底线[...]*.由于某人不是初学者,不是专业人士,OP的代码很难理解.它可以作为一个有趣的事实(如果它实际上,看到其他答案),但消息是模糊的,它不应该被提升为"另一种方法",而应该作为一个"有趣的事实",明确地. (3认同)

eer*_*ika 7

正如其他答案所解释的那样,代码违反了类型别名规则,并做出了标准无法保证的假设.

如果您真的想手动执行此优化,这将是一种具有明确定义的行为的正确方法:

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);
}
Run Code Online (Sandbox Code Playgroud)

关于对象大小的不安全假设通过使用来修复sizeof,并且通过使用修复了别名冲突std::memcpy,其具有明确定义的行为,而不管底层类型如何.

也就是说,最好保持代码简单,让编译器发挥其魔力.


为什么我需要左移操作符?

关键是用较小整数的多个副本填充一个更大的整数.如果你把一个两字节的值写成一个s大整数l,那么剩下的位移到两个字节(我的固定版本应该更清楚那些魔术数字的来源)然后你将有一个带有两个字节副本的整数构成价值s.重复此过程,直到所有字节对l都设置为相同的值.要进行班次,您需要班次操作员.

当这些值复制到包含双字节整数数组的数组上时,单个副本会将多个对象的值设置为较大对象的字节值.由于每对字节具有相同的值,因此数组的较小整数也是如此.

为什么我需要另一个阵列long

没有阵列long.只有一个数组short.


Yak*_*ont 6

您的老师向您展示的代码是一个格式错误的程序,不需要诊断,因为它违反了指针实际指向他们声称指向的东西的要求(也称为"严格别名").

作为一个具体的例子,编译器可以分析你的程序,注意A没有直接写入并且没有short写入,并且证明A一旦创建就永远不会改变.

B根据C++标准,所有这些都可以证明,因为无法A在格式良好的程序中进行修改.

一个for(;;)循环甚至一个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)... }};
  });
}
Run Code Online (Sandbox Code Playgroud)

现在:

int main() {

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

  std::cout << "\n";
  print(A.data(),100);
}
Run Code Online (Sandbox Code Playgroud)

在编译时创建填充数组,不涉及循环.

使用godbolt可以看到数组的值是在编译时计算的,当我访问第50个元素时,值3被提取出来.

然而,这是过度杀伤(和).