登录 立即注册

找到2210个回复 (用户: 老虎会游泳)

老虎会游泳 35楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,根据以下文段,我觉得所有权的转移是在声明时发生的,而非使用时发生的。所以只要这个定义域内存在“restrict 指针 P”,就不能通过其他手段访问。

在每个声明了 restrict 指针 P 的块(典型例子是函数体的执行,其中 P 为参数)中,若某个可由 P (直接或间接)访问的对象会被任何手段修改,则该块中所有对该对象(读或写)的访问,都必须经由 P 出现,否则行为未定义。

只有一种情况可以存在其他别名:“restrict 指针 P”指向的内容不会进行任何修改。

若对象决不被修改,则它可以被别名引用,并被异于 restrict 限定的指针访问。

老虎会游泳 33楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,对,restrict的作用应该是所有权的转移,规则应该和Rust类似:所有权转移给某别名之后,就不能再用其他别名访问了,但是转移之前则可以。

老虎会游泳 31楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,顺便一提,在安卓上long是64位,这就是符号位差异的来源

Screenshot_20230127_170107.jpg(97.4 KB)

还有,memcpy看起来是最佳选择,因为它没有任何多余的操作——我们想要的就是内存复制,所以我们就应该写内存复制。改成memcpy后,代码比用联合与指针类型转换都简单。

#include <stdio.h>
#include <stdint.h>                                                                                     float Q_rsqrt( float number ) {
  int32_t i = 0;
  const float threehalfs = 1.5F;

  float x2 = number * 0.5F;
  float y  = number;
  memcpy(&i, &y, sizeof(y));                       // evil floating point bit level hacking
  i  = 0x5f3759df - ( i >> 1 );               // what the fuck?
  memcpy(&y, &i, sizeof(y));
  y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

  return y;
}

int main() {
  printf("sizeof(long):    %lu\n", sizeof(long));
  printf("sizeof(int32_t): %lu\n", sizeof(int32_t));
  printf("sizeof(float):   %lu\n", sizeof(float));

  printf("%0.7f\n", Q_rsqrt(3.14));
  printf("%0.7f\n", Q_rsqrt(1024.0));
  printf("%0.7f\n", Q_rsqrt(10086.0));
  printf("%0.7f\n", Q_rsqrt(2147483647.0));

  printf("%0.14f\n", Q_rsqrt(3.14));
  printf("%0.14f\n", Q_rsqrt(1024.0));
  printf("%0.14f\n", Q_rsqrt(10086.0));
  printf("%0.14f\n", Q_rsqrt(2147483647.0));

  return 0;
}
老虎会游泳 29楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,你说得对,( long * ) &y是左值,* ( long * ) &y是对它求值。

不过Q_rsqrt()改写成符合标准似乎很容易。

还有符号位的处理与union版本存在差异,所以确实可能涉及未定义行为。得到负数解可能才是这种内存操作应该有的结果。

老虎会游泳 26楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,如果特别担心,就用联合吧。

#include <stdio.h>

float Q_rsqrt( float number ) {
  union { long l; float f; } i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i.f = y;                       // evil floating point bit level hacking
  i.l  = 0x5f3759df - ( i.l >> 1 );               // what the fuck?
  y  = i.f;
  y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

  return y;
}

int main() {
  printf("%0.7f\n", Q_rsqrt(3.14));
  printf("%0.7f\n", Q_rsqrt(1024.0));
  printf("%0.7f\n", Q_rsqrt(10086.0));
  printf("%0.7f\n", Q_rsqrt(2147483647.0));

  printf("%0.14f\n", Q_rsqrt(3.14));
  printf("%0.14f\n", Q_rsqrt(1024.0));
  printf("%0.14f\n", Q_rsqrt(10086.0));
  printf("%0.14f\n", Q_rsqrt(2147483647.0));

  return 0;
}

代码实际上变简单了。

不过有趣的是,使用联合的版本给出的都是负值(虽然也是正确解,平方根有两个解),不知道符号位的处理和非联合版本有什么不同。

Screenshot_20230127_164309.jpg(144.41 KB)

老虎会游泳 23楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,经过一番思考之后,我还是认为i = * ( long * ) &y没有问题,因为没有生成新的别名,整个表达式应该被视为一个右值。

相反,把它分开的操作反而是有问题的,这违反了严格别名规则。

long i;  long *p_i;
float y; float *p_y;

p_y = &y;
p_i = (long *) p_y;
i = *p_i;

但它应该也没有副作用,因为不涉及对*p_i的写入。


最重要的是,i = * ( long * ) &y的目标是读取y的值,它根本没有任何优化空间。&y意味着y一定得在内存,所以无论怎么优化,结果应该都是正确的。

老虎会游泳 18楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,这里没有未定义行为,因为取地址操作会阻止优化。因为&a,所以a必须在内存,不能优化到寄存器。所以该代码没有未定义行为,但存在出现编程错误的风险(如果float和long长度不同)。

float a = 1.0;
long * b = (long *)&a;

*b = 1;
return a;
老虎会游泳 16楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,我还是要说,错误行为不是未定义行为。

解引用指向float值的long指针具有明确的定义,因为float的内存表示在IEEE754定义,long的内存表示在C中定义。在特定的实现中,两者的长度可能相同,也可能不同,但当两者长度不同时,错误一定会以规定好的方式发生:float及其后不属于它的4字节会被访问。这只是编程错误,不是未定义行为。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1N1bkNoZXJyeURyZWFt,size_16,color_FFFFFF,t_70.jpg(11.22 KB)

老虎会游泳 14楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

需要注意的是,错误行为不是未定义行为

char c;
long i;

// 这个行为非常不恰当,会导致紧接着`c`后面的3个字节被访问,这3个字节不属于`c`。
// 但它只是错误行为,不是未定义行为。
// 这个行为会发生什么具有明确的定义,就是`c`所指向的内存地址及其后方3个字节一同被赋值给`i`,在所有平台上都会发生同样的事情。
// 所以,这里不含未定义行为,只含编程错误。
i = * ( long * ) &c;
老虎会游泳 12楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,我已经对上述问题进行了回答。

老虎会游泳 10楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,把这段代码拆分成多个部分,应该有助于理解为什么没有未定义行为:

long i;  long *p_i;
float y; float *p_y;

p_y = &y; // 只是一个简单的取地址操作,不是未定义行为
p_i = (long *) p_y; // 对指针进行类型转换不是未定义行为,所有指针类型都是互相兼容的
i = *p_i; // i 和 *p_i 类型一致,没有未定义行为

操作的每一步都不含未定义行为,所以整体不含未定义行为。

老虎会游泳 9楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊i = * ( long * ) &y不含未定义行为,因为long i,所以* ( long * )long显然是它的兼容类型。当赋值发生时,类型已经是long了。而把一个float指针转换为long指针显然也不是未定义行为,因为实际上只是绕过了编译器的类型检查,对于代码生成来说相当于什么也没有发生,指针的值没有任何变化。

老虎会游泳 1楼回复 tasy5kg打游戏总是瞄不准,可能真的是鼠标原因 (2023-01-27//)

鼠标垫脏了或者不平也会有这种现象

老虎会游泳 7楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

至于 i = 0x5f3759df - ( i >> 1 ) 到底意味着什么,其实也可以有纯数学的解释。

0x5f3759dfi 其实都是浮点数,但是使用整数规则进行了运算,这些运算同时操作了浮点数的指数和尾数部分。

比如 i >> 1 也就是把指数和尾数同时向后挪动一位,两者的最后一位都被抛弃,然后指数的最后一位变成尾数的第一位。

0x5f3759df - $x 也就是把指数和尾数同时减小,并且尾数减到小于0时向指数借位。

这些操作都可以写成数学公式,从而让运算具有数学上的解析表达——也就是说,运算结果是确定的,没有未定义行为。

老虎会游泳 6楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,这是这个函数的PHP版本,有助于理解为什么没有未定义行为:

<?php
function Q_rsqrt(float $number) {
    $threehalfs = 1.5;
    $x2 = $number * 0.5;
    $y = $number;

    $i = unpack("l", pack("f", $y))[1];
    $i = 0x5f3759df - ($i >> 1);
    $y = unpack("f", pack("l", $i))[1];
    $y = $y * ( $threehalfs - ($x2 * $y * $y) );
    $y = $y * ( $threehalfs - ($x2 * $y * $y) );

    return $y;
}

printf("%0.7f\n", Q_rsqrt(3.14));
printf("%0.7f\n", Q_rsqrt(1024.0));
printf("%0.7f\n", Q_rsqrt(10086.0));
printf("%0.7f\n", Q_rsqrt(2147483647.0));

printf("%0.14f\n", Q_rsqrt(3.14));
printf("%0.14f\n", Q_rsqrt(1024.0));
printf("%0.14f\n", Q_rsqrt(10086.0));
printf("%0.14f\n", Q_rsqrt(2147483647.0));

在给定的定义域和有效数字范围内,它和C版本的结果一致。如果继续增加输出的位数,结果就开始不一致了,因为PHP在内部使用64位整数和双精度浮点数,而非C代码的32位整数和单精度浮点数,只在packunpack时才转换为32位单精度,所以两者会有精度差异。

此外32位和64位在处理符号位上可能也有差异,所以C版给出负数解的情况下PHP给出的是正数解。当然两者都是正确的解,因为负数的平方也是正数。

Screenshot_20230127_144400.jpg(159.56 KB)

C版本:

#include <stdio.h>

float Q_rsqrt( float number ) {
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i  = * ( long * ) &y;                       // evil floating point bit level hacking
  i  = 0x5f3759df - ( i >> 1 );               // what the fuck?
  y  = * ( float * ) &i;
  y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

  return y;
}

int main() {
  printf("%0.7f\n", Q_rsqrt(3.14));
  printf("%0.7f\n", Q_rsqrt(1024.0));
  printf("%0.7f\n", Q_rsqrt(10086.0));
  printf("%0.7f\n", Q_rsqrt(2147483647.0));

  printf("%0.14f\n", Q_rsqrt(3.14));
  printf("%0.14f\n", Q_rsqrt(1024.0));
  printf("%0.14f\n", Q_rsqrt(10086.0));
  printf("%0.14f\n", Q_rsqrt(2147483647.0));

  return 0;
}
老虎会游泳 3楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊,此外,Q_rsqrt()函数中没有未定义行为,IEEE 754 标准已经精确的定义了单精度浮点数(float)的二进制表示,所以把它的二进制表示做为long使用不是未定义行为,结果应该是很明确的:符号位依然是符号位,指数和尾数则被拼接在一起做为整数的值。

反向操作(把整数的二进制表示做为单精度浮点数使用)结果也很明确:符号位依然是符号位,然后接下来8位成为指数,最后23位成为尾数。

所以,这只是一个“用户定义浮点数算法”,它与GMP等其他用户定义数学库中的自定义浮点数算法没有本质区别。代码中的每次类型转换在C中都有明确的定义。在所有使用IEEE754单精度浮点数的计算机中,结果都应该是一致的。

老虎会游泳 5楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

其实无论是const,还是volatilerestrict,都是为了解决内存空间的所有权问题。

因为C/C++可以操作原始指针,所以内存空间的所有权可以在多个线程、函数、变量之间以任意方式共享和转移,导致编译器优化很容易出问题,所以才需要这些标记加以指示。

其他编程语言不能直接操作原始指针,所以内存空间的所有权是明确的,不需要这些编译器优化限定符。

当然const也有语法上的含义,表明你希望编译器帮你阻止对该变量的修改,所以其他编程语言里也存在该关键字。但是volatilerestrict在语法上没有任何含义,所以在内存空间所有权明确的编程语言中完全不存在。不能对原始指针解引用的语言都是所有权明确的,带GC的语言通常属于此类。

所谓原始指针解引用,就是类似这样的代码:

y  = * ( float * ) &i;

它在语法上提供了无限的灵活性,实际上可以用于读写任意内存地址:

int main() {
    long i = 1;
    float y = -1;
    const int x = 12306;

    // 以下代码没有语法错误,可以编译通过。

    // 读取原始指针
    y  = * ( float * ) (&i + 10086);
    y  = * ( float * ) 10086;

    // 写入原始指针
    * ( float * ) (&i + 10086) = y;
    * ( float * ) 10086 = y;

    // 写入 const 变量
    * (int *) &x = 10010;

    return 0;
}

因为这种灵活性,所以在C/C++中跟踪内存空间所有权变得不可能,于是需要对所有权进行人工标记。而constvolatilerestrict正是这样的标记。

const:我保证不写入这块内存空间。如果我通过原始指针解引用实现了写入,结果是未定义的。
restrict:我保证不把内存空间的所有权转移给其他变量(也就是创建别名)。如果我确实转移了,结果是未定义的。
volatile:我对该内存空间的使用不进行任何保证,请不要假设它可以被优化。至于到底能阻止哪些优化,由实现定义。

需要说明的是:volatile不是线程同步措施,它不能提供多核CPU间的内存一致性。想实现多线程内存一致性必须使用同步原语(比如互斥锁 mutex)。

老虎会游泳 4楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

一个指针经 restrict 修饰后,它(可能经过指针运算后)指向的对象不会不能有其它别名。

并非不会,而是不能

不会意味着编译器会阻止你为它创建别名,创建别名会导致编译错误。

但实际上只是不能,创建别名最多产生警告,程序还是能运行,而且还可能完全无错(因为编译器优化后程序出问题只是概率事件)。

所以,restrict体现的是你的自信,你得首先保证你的代码没有对该变量创建别名,然后才能给它加上restrict

就像volatile,是你不自信,觉得优化这个变量会出问题,才给它加上volatile。至于不加会不会出问题,得具体问题具体分析。

老虎会游泳 2楼回复 无名啊如何理解 C/C++ 中的 指针别名(pointer alias)、restrict、const 的关系呢? (2023-01-27//)

@无名啊volatilerestrict是编译器优化指示标记,其中volatile阻止对该标识符进行优化,restrict建议编译器对该标识符进行优化。

volatile的语义:小心,这个变量的用途很复杂,优化这个变量很可能会导致程序出问题!

restrict的语义:我保证我只通过这个变量访问它指向的内存区域,你随便优化它,绝对不会出问题!

这些都只是给编译器的提示,编译器不一定会遵循指示。比如,使用-O0编译时,加不加volatilerestrict参数都没有任何区别。只有-O1-O2-O3等有区别。

对于VC++编译器,Debug模式应该体现不出区别,只有Release模式才有区别。

const与它们不一样,它不仅是编译器优化指示标记,还进行了语法上的限制。如果不通过强制类型转换去除const标记,则无法对变量进行写入。

不过,因为const也是编译器优化指示标记,它的语义是:我保证不会对该变量进行写入,你放心优化。所以如果后续通过强制类型转换去掉const并写入变量,则Release版程序可能会出问题。注意只是可能,编译器会尽量给出不出问题的代码,所以真想遇到问题也需要碰运气。

老虎会游泳 24楼回复 ysyvsllinux上古卷轴5怎么安装mod? (2023-01-26//)

已经添加了一个Vortex模组管理器,亲测可以正常安装mod

https://winegame.net/games/vortex-mod-manager/

下一页 上一页 (42 / 111页)

10月24日 08:36 星期五

本站由hu60wap6驱动

备案号: 京ICP备18041936号-1