在循环条件下:为啥 strlen() 每次都被调用,而 vector.size() 只被调用一次? [复制]

Posted

技术标签:

【中文标题】在循环条件下:为啥 strlen() 每次都被调用,而 vector.size() 只被调用一次? [复制]【英文标题】:In loop condition: why is strlen() called every time, but vector.size() gets called only once? [duplicate]在循环条件下:为什么 strlen() 每次都被调用,而 vector.size() 只被调用一次? [复制] 【发布时间】:2018-10-28 14:07:29 【问题描述】:

代码:

#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;

void f(const char* s) 
    for (size_t i = 0; i < strlen(s); i++) 
        printf("%c ", s[i]);
    


void g(const vector<int>& v) 
    for (size_t i = 0; i < v.size(); i++) 
        printf("%d ", v[i]);
    

编译链接:https://godbolt.org/z/PCi5yg

你会看到汇编代码:

在函数f()中,strlen(s)每次都会被调用; 但在函数 g() 中,v.size() 只被调用一次。

这是为什么呢?

参数为const,在内循环中没有改变参数。

【问题讨论】:

嗯,向量是const,但strlen 不知道const,s 不是const。 可以在扫描C风格字符串的代码中使用for (size_t i = 0; s[i] != '\0'; i++),避免使用strlen() 请记住,strlen 必须访问字符串中的每个字符,直到找到终止的 nul 字符。如果 C-Style 字符串在循环内没有改变,这可能是非常低效的。 std::vector 可以将其大小存储在成员变量中,因此可以快速返回大小;与std::string类似。 【参考方案1】:

您误解了生成的程序集。并不是假定向量的大小不变,只是对v.size() 的调用是内联的。向量的大小仍然在每次循环迭代时重新计算并加载到rax

const 限定符仅防止函数fg 修改它们引用的对象。这并不意味着他们可以假设对象是真正不可变的并且不能改变大小。编译器必须假设非内联函数可能会改变向量,包括printf。所以函数必须重新计算向量的大小才能正确。

您可以进一步说服自己,即使经过优化,turning off inlining (-fno-inline) 也会重新计算大小。生成的程序集非常清楚地展示了对vector::size 的调用。

【讨论】:

@Broman - 别担心。 West const 有时甚至会误导最好的人。 是的,你是对的!我误解了生成的程序集。正如您所说:Another thread of execution theoretically could be changing the vector,所以编译器在大多数情况下不会缓存该值?我们应该在循环之前计算它吗?谢谢! @HangYuan - 不能告诉你你应该做什么。这取决于您的应用程序及其需求。但我可以告诉您,只要您编写正确的程序(我的意思是正确的,没有未定义的行为等),符合标准的 c++ 实现将尽力产生正确的行为。 @StoryTeller 非常感谢! 理论上另一个执行线程可能正在改变向量。不,编译器确实根据 that 是 UB (MCU programming - C++ O2 optimization breaks while loop) 进行优化。但是对像printf 这样的非内联函数的调用可以改变任何东西,除了escape analysis 证明它不能的本地状态。如果循环中没有任何非内联函数调用,别名分析可能能够证明提升strlenv.size() 是安全的。

以上是关于在循环条件下:为啥 strlen() 每次都被调用,而 vector.size() 只被调用一次? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如果在循环条件中使用 strlen 会被计算多次吗?

是否每次都为 Iterables 调用 for 循环的“条件”?

关于在判断条件中使用strlen()函数的问题

CentOS Linux hosts 文件为啥每次启动都被修改

方法不是每次都被调用

java for循环 判断条件为空时 构造方法为啥不需要返回值