将数组的所有元素初始化为相同的数字
前段时间,我的老师发布了这个代码,说这是将数组初始化为相同数字(当然不是零)的另一种方法。
三在这种情况下。
他说这种方式比for
循环要好一些。 为什么我需要左移操作符? 为什么我需要另一个长arrays? 我不明白这里发生了什么。
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); }
他假设long
是short
四倍(这是不能保证的;他应该使用int16_t和int64_t)。
他需要更长的存储器空间(64位)并用四个短的(16位)值填充。 他通过移位16个空格来设置值。
然后他想把一个短arrays作为一个long数组,所以他可以通过只做25个循环而不是100个循环来设置100个16位值。
这就是你的老师的想法,但正如其他人所说,这个演员是不明确的行为。
有很多方法来填充具有相同值的数组,如果您担心性能,那么您需要进行测量。
C ++有一个用数组填充数组的专用函数,我会使用它(在#include <algorithm>
和#include <iterator>
):
std::fill(std::begin(A), std::end(A), 3);
你不应该低估什么样的优化编译器可以做这样的事情。
如果您有兴趣了解编译器的function,那么如果您准备学习一些汇编语言,那么Matt Godbolt的Compiler Explorer就是一个非常好的工具。 正如你从这里可以看到的,编译器可以优化fill
调用到十二(和一点)128位商店与任何循环展开。 因为编译器具有目标环境的知识,所以他们可以在不编码源代码中任何特定于目标的假设的情况下做到这一点。
什么是hogwash的绝对负载。
-
对于初学者,
v
将在编译时计算。 -
取消引用
B
的行为long *B = (long*)A;
是不确定的,因为这些types是不相关的。B[i]
是B
的解引用。 -
没有任何理由假设
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);
另外,老师正在试图智取编译器,这个编译器可以做令人兴奋的事情。 如果configuration正确,编译器可以为你做这样的技巧是没有意义的:
- 你的代码程序集
- 您的代码程序集删除了刻度
- 数组方法
- vector的方法
正如你所看到的,每个版本的-O2
结果代码是(几乎)相同的。 在-O1
情况下,窍门有所改善。
所以底线,你必须做出select:
- 编写难以阅读的代码,不要使用编译器优化
- 编写可读代码并使用
-O2
使用Godbolt网站尝试其他编译器和configuration。 另见最新的cppCon谈话 。
正如其他答案所解释的那样,代码违反了types别名规则,并且做出了不被标准保证的假设。
如果你真的想用手做这种优化,这将是一个有明确定义的行为的正确方法:
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
来修复混叠违例,无论基础types如何,该std::memcpy
都有明确定义的行为。
也就是说,最好让你的代码简单一些,让编译器做它的魔法。
为什么我需要左移操作符?
重点是用较小整数的多个副本填充一个更大的整数。 如果你把一个两字节的值写成一个大的整数l
,那么把这两个字节左移两个字节(我的固定版本应该更清楚这些神奇数字来自哪里),那么你将得到一个整数,构成值s
字节。 这一直重复,直到l
中的所有字节对被设置为相同的值。 为了完成这个转换,你需要移位操作符。
将这些值复制到包含两字节整数数组的数组上时,单个副本将将多个对象的值设置为较大对象的字节值。 由于每对字节具有相同的值,所以数组的较小整数也是如此。
为什么我需要另一个
long
arrays?
没有long
arrays。 只有一小排。
你的老师向你显示的代码是一个不合格的程序,不需要诊断,因为它违反了指针实际上指向他们声称指向的东西的要求(否则称为“严格别名”)。
作为一个具体的例子,编译器可以分析你的程序,注意到A
没有被直接写入,也没有写入短语,并且certificateA
一旦创build就永远不会被改变。
所有那些搞乱B
都可以在C ++标准下被certificate是不能在一个合适的程序中修改A
一个for(;;)
循环甚至一个ranged-for可能被优化到A
静态初始化。 您的老师的代码,在优化编译器下,将优化到未定义的行为。
如果你确实需要一种方法来创build一个用一个值初始化的数组,你可以使用这个:
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); }
在编译时创build填充数组,不涉及循环。
使用godbolt,你可以看到数组的值是在编译时计算出来的,当我访问第50个元素时,数值3被提取出来。
但是,这是过度杀伤(和C ++ 14 )。
我想他正试图通过同时复制多个数组元素来减less循环迭代次数。 正如其他用户已经在这里提到的,这个逻辑会导致未定义的行为。
如果全部是关于减less迭代,那么使用循环展开我们可以减less迭代次数。 但是对于这样小的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); }