如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢?

回复列表(80|隐藏机器人聊天)
  • @Ta / 2023-01-27 / /

    @无名啊this可能已经优化到了寄存器中。

    T::unpack3bit 每次移位之前都重新获取 target 变量的地址放到 rdi 中, mov rdi, QWORD PTR [rdx] ,而 target 整个过程没有改变,这样做没有必要。

    这里是在读取target指针的值,mov rdi, QWORD PTR [rdx],是以rdx寄存器的值为内存首地址,连续读取8字节到rdi寄存器。

    因为targetstruct T的第一个成员,所以它的首地址就是this。也就是说,thisrdx寄存器。

    所以问题是程序一直在从内存加载this->target,而非一直在从内存加载this

  • @Ta / 2023-01-27 / /

    @无名啊,对了,VC++的__thiscall调用约定始终将this指针放在ecx寄存器。

    https://learn.microsoft.com/zh-cn/cpp/cpp/thiscall?view=msvc-170

    __thiscall 的调用约定用于 x86 体系结构上的 C++ 类成员函数。 它是成员函数使用的默认调用约定。

    在 __thiscall 下,被调用方清理堆栈,自变量将从右到左推送到堆栈中。 指针 this 通过寄存器 ECX 传递,而不是在堆栈上传递。

  • @Ta / 2023-01-27 / /

    @老虎会游泳,噢,是我想错了 编译器认为 this 没被改,但 this->target 可能被改了。。

    x64 时,this 作为第一个参数,存在 rdi 中?

  • @Ta / 2023-01-27 / /

    @无名啊,上面的代码:

    因为target是struct T的第一个成员,所以它的首地址就是this。也就是说,this在rdx寄存器。

    x64调用约定

    整数参数在寄存器 RCX、RDX、R8 和 R9 中传递。

    所以this既然在RDX中,那么它应该是第二个整数参数,第一个可能是返回地址。

  • @Ta / 2023-01-27 / /

    https://blog.csdn.net/xkdlzy/article/details/108873014

    小于等于64位的整型或指针类型返回值由RAX传递。
    浮点返回值由XMM0传递。
    更大的返回值(比如结构体),由调用方在栈上分配空间,并有RCX持有该空间的指针并传递给被调用函数,因此整型参数使用的寄存器依次右移一格,实际只可以利用3个寄存器,其余参数入栈。函数调用结束后,RAX返回该空间的指针。

    但函数没有返回值(void),所以不清楚上面的案例中RCX用于什么,x64调用约定没有明说。

  • @Ta / 2023-01-27 / /

    @无名啊,关于

    老虎用 restrict 的情景多吗?

    我以前不知道restrict和“严格别名”,今天刚知道。我以前唯一了解的所有权转移情形是std::move()

  • @Ta / 2023-01-27 / /

    编译器认为 this 没被改,但 this->target 可能被改了。。

    其实这句话没说错:

    因此,每次修改 target,编译器认为 this 也可能随之变化,即 target[0] = t & 0x7; 可能改变了 this 指针。

    因为this(也就是RDX寄存器的值)可能变了,所以才需要重新加载this->target

    那为什么不需要重新加载this呢?因为它就在寄存器,所以自然不需要重新加载,直接用就好了。

  • @Ta / 2023-01-28 / /

    @老虎会游泳,最后居然聊了这么远。。扯到汇编去了(但 C/C++ 本来就和底层接近,谈及汇编来理解 C/C++ 行为,似乎也很正常。。)

    老虎你写商业 C++ 代码的,都不知道 restrict,看来 C/C++ 的效率足够绝大部分场景使用,是我杞人忧天了。

  • @Ta / 2023-01-31 / /

    我还在过年放鞭炮,你们就开始讨论这么高深的内容了。
    小米MIX2s(白)

  • @Ta / 2023-02-01 / /

    @水木易安,其实,这是大一学的 C 语言里的内容。。但我最近才重新注意到它。。

    但看起来,写商业 C++ 代码的老虎,这么多年都不熟悉这个,看来是没啥用的特性

  • @Ta / 2023-02-01 / /

    @无名啊,你的大学我的大学,好像不一样
    小米MIX2s(白)

  • @Ta / 2023-02-01 / /

    @水木易安,不会吧?我记得理工科的都要学 C 啊。。

  • @Ta / 2023-02-01 / /

    @无名啊,是学 c,但是那时候大家都是在学习搞开发环境、变量、语句、条件控制,循环、函数和重载、指针、结构体。考试写个水仙花数,判断个闰年平年就差不多了吧

    不会一上来就学习编译器,词法语法分析吧

    寄存器、汇编好像是大三开始学习的。
    小米MIX2s(白)

  • @Ta / 2023-02-01 / /

    @无名啊@水木易安,大学肯定不会教这个啊,大学教的C和C++基本上都是最具可移植性的部分,语法基本上停留在C89阶段,不会有C99之后的新特性。

  • @Ta / 2023-02-01 / /

    @水木易安,呃。。我的意思是,这些都只是 C 语言的(关键字级别的)基础内容。

    只不过有点偏,连老虎写了多年 C++ 代码的都没注意(我也只是最近写 C,查语言标准,看自己代码能不能这样写时,偶然注意到

    我们的课程没有 8086/x86/x64/arm/wasm/... 汇编。。我只是凭以前看过的半本 x86 汇编,和老虎聊上一两句而已。。

    这个帖子好像没涉及到编译原理中的词法语法分析,有谈到编译器,但也仅是猜测它的行为

    @老虎会游泳,是的。但有时候也教 UB 代码,比如 i++ + ++i 啥的

  • @Ta / 2023-02-01 / /

    @无名啊,我看出一些问题。
    restrict是一个C99规定的关键字,C++的任何版本都未要求实现该关键字,大部分C++编译器会直接忽略它。
    你是想在C++中用它吗,那你应该用__restrict__,虽然它是编译器特定扩展,但大部分编译器都实现了它。

  • @Ta / 2023-02-01 / /

    @无名啊i++ + ++i中哪一部分是未定义的?C标准允许++i读取不到i++增加后的值?

  • @Ta / 2023-02-01 / /

    @老虎会游泳,我现在不想碰 restrict,因为:

    • C/C++ 本身速度足够快,我目前不需要写性能极度敏感的代码
    • 弄清楚编译器如何对待 restrict 估计很耗时间。。

    cppreference - 求值顺序 说:

    除下列标出者,任意 C 运算符的运算数求值顺序,包括函数调用表达式的函数参数求值顺序,及任何表达式的子表达式求值顺序都是未指定的。编译器会以任意顺序对其求值,而且在同一表达式被再度求值时可选用另一种顺序。

    C 中没有从左到右或从右到左求值的概念,这不会与运算符的从左到右或从右到左结合性混淆:表达式 f1() + f2() + f3() 被分析成 (f1() + f2()) + f3() ,因为 operator+ 的从左到右结合性,但运行时对 f3 的函数调用可以最先、最后,或在 f1() 与 f2() 之间求值。

    未定义行为

    1. 若对一个标量对象的副效应与另一个对同一标量对象的副效应相对无顺序,则行为未定义
    i = ++i + i++; // 未定义行为
    i = i++ + 1; // 未定义行为
    f(++i, ++i); // 未定义行为
    f(i = -1, i = -1); // 未定义行为
    1. 若一个标量对象上的副效应与另一个使用同一标量对象之值的值计算相对无顺序,则行为未定义。
    f(i, i++); // 未定义行为
    a[i] = i++; // 未定义行为
    1. 只要至少一个子表达式的排序容许这种无顺序副效应,就应用上述规则。

    我记得,C 只保证在序列点后,所有副作用都被执行。

  • @Ta / 2023-02-01 / /

    @无名啊,嗯,看起来C为并行执行优化留下了很多空间。

  • @Ta / 2023-10-24 / /

    @无名啊,我刚看到C++20的std::bit_cast,这应该是在C++中实现类型双关的唯二安全方法(另一种安全方法是memcpy)。C的话,联合应该是安全方法。

    https://en.wikipedia.org/wiki/Fast_inverse_square_root 给出的安全版本:

    // C
    # include <stdint.h> // uint32_t
    
    float q_rsqrt(float number)
    {
      union {
        float    f;
        uint32_t i;
      } conv = { .f = number };
      conv.i  = 0x5f3759df - (conv.i >> 1);
      conv.f *= 1.5F - (number * 0.5F * conv.f * conv.f);
      return conv.f;
    }
    
    // C++20
    # include <bit>
    # include <limits>
    # include <cstdint>
    
    constexpr float Q_rsqrt(float number) noexcept
    {
      static_assert(std::numeric_limits<float>::is_iec559); // (enable only on IEEE 754)
    
      float const y = std::bit_cast<float>(
        0x5f3759df - (std::bit_cast<std::uint32_t>(number) >> 1));
      return y * (1.5f - (number * 0.5f * y * y));
    }
    
添加新回复
回复需要登录