[英]Initialize all the elements of an array to the same number
前段时间,我的老教师发布了这段代码,说这是将数组初始化为相同数字的另一种方法(当然不是零)。
在这种情况下三个。
他说这种方式比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);
}
有很多方法可以用相同的值填充数组,如果你关心性能,那么你需要测量。
C ++有一个专用的函数来填充一个值的数组,我会使用它(在#include <algorithm>
和#include <iterator>
):
std::fill(std::begin(A), std::end(A), 3);
你不应该低估优化编译器可以用这样的东西做什么。
如果您有兴趣了解编译器的功能,那么Matt Godbolt的Compiler Explorer是一个非常好的工具,如果您准备学习一点汇编程序的话。 从这里可以看出,编译器可以优化fill
调用到12个(和一些)128位存储,并且展开任何循环。 因为编译器具有目标环境的知识,所以他们可以在不编码源代码中的任何特定于目标的假设的情况下执行此操作。
他假设long
是short
四倍(不能保证;他应该使用int16_t和int64_t)。
他占用了更长的内存空间(64位)并用四个短(16位)值填充它。 他通过将位移16个空格来设置值。
然后他想将一组shorts视为long数组,因此他只需要25次循环迭代而不是100次就可以设置100个16位值。
这就是你的老师的想法,但正如其他人所说,这种演员是不明确的行为。
什么是hogwash的绝对负荷。
对于初学者, v
将在编译时计算。
long *B = (long*)A;
取消引用B
的行为long *B = (long*)A;
未定义,因为类型不相关。 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);
此外,老师正在尝试智能编解器,这可以做令人兴奋的事情。 没有必要做这样的技巧,因为如果配置正确,编译器可以为你做这些:
如您所见,每个版本的-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
来修复别名违规, std::memcpy
具有明确定义的行为,而不管底层类型如何。
也就是说,最好保持代码简单,让编译器发挥其魔力。
为什么我需要左移操作符?
关键是用较小整数的多个副本填充一个更大的整数。 如果你写了一个双字节值s
来了个大整数l
,然后移位留给两个字节(我的固定版本应该是在哪里那些神奇的数字是从哪里来的更清晰),那么你就必须与这两个副本的整数构成值s
字节。 重复这一过程,直到l
中的所有字节对都设置为那些相同的值。 要进行班次,您需要班次操作员。
当这些值复制到包含双字节整数数组的数组上时,单个副本会将多个对象的值设置为较大对象的字节值。 由于每对字节具有相同的值,因此数组的较小整数也是如此。
为什么我需要另一个
long
阵列?
没有long
阵列。 只有short
。
您的老师向您展示的代码是一个格式错误的程序,不需要诊断,因为它违反了指针实际指向他们声称指向的东西的要求(也称为“严格别名”)。
作为一个具体的例子,编译器可以分析你的程序,注意A
没有被直接写入并且没有写入short
,并证明A
一旦创建就永远不会改变。
根据C ++标准,所有与B
混乱的事情都可以证明,因为无法在格式良好的程序中修改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)... }};
});
}
现在:
int main() {
auto A = make_filled_array<short, 100, 3>();
std::cout << "\n";
print(A.data(),100);
}
在编译时创建填充数组,不涉及循环。
使用godbolt可以看到数组的值是在编译时计算的,当我访问第50个元素时,值3被提取出来。
然而,这是过度杀伤(和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);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.