为什么pthread_self用属性(const)标记?
在Glibc的为什么pthread_self用属性(const)标记?,c,gcc,pthreads,glibc,C,Gcc,Pthreads,Glibc,在Glibc的pthread.h中,使用const属性声明pthread\u self函数: extern pthread_t pthread_self (void) __THROW __attribute__ ((__const__)); 在GCC中: 许多函数除了参数之外不检查任何值,并且除了返回值之外没有任何效果。基本上,这个类比下面的纯属性稍微严格一点,因为函数不允许读取全局内存 我想知道这是怎么回事?由于它不接受任何参数,pthread\u self因此只允许始终返回相同的值,这显然
pthread.h
中,使用const
属性声明pthread\u self
函数:
extern pthread_t pthread_self (void) __THROW __attribute__ ((__const__));
在GCC中:
许多函数除了参数之外不检查任何值,并且除了返回值之外没有任何效果。基本上,这个类比下面的纯属性稍微严格一点,因为函数不允许读取全局内存
我想知道这是怎么回事?由于它不接受任何参数,pthread\u self
因此只允许始终返回相同的值,这显然不是事实。也就是说,我希望pthread\u self
读取全局内存,因此最终被标记为pure
:
许多函数除了返回值之外没有任何效果,它们的返回值仅取决于参数和/或全局变量。像算术运算符一样,这样的函数可以进行公共子表达式消除和循环优化。这些函数应该用pure属性声明
x86-64上的实现似乎实际上正在读取全局内存:
# define THREAD_SELF \
({ struct pthread *__self; \
asm ("mov %%fs:%c1,%0" : "=r" (__self) \
: "i" (offsetof (struct pthread, header.self))); \
__self;})
pthread_t
__pthread_self (void)
{
return (pthread_t) THREAD_SELF;
}
strong_alias (__pthread_self, pthread_self)
这是虫子还是我没看到什么
我想知道这是怎么回事
此属性告诉编译器,在给定的上下文中,pthread\u self
将始终返回相同的值。换句话说,下面的两个循环完全等效,编译器可以优化对pthread\u self
的第二次(以及所有后续)调用:
// loop A
std::map<pthread_t, int> m;
for (int j = 0; j < 1000; ++j)
m[pthread_self()] += 1;
// loop B
std::map<pthread_t, int> m;
const pthread_t self = pthread_self();
for (int j = 0; j < 1000; ++j)
m[self] += 1;
//循环A
std::map m;
对于(int j=0;j<1000;++j)
m[pthread_self()]+=1;
//回路B
std::map m;
const pthread_t self=pthread_self();
对于(int j=0;j<1000;++j)
m[self]+=1;
x86-64上的实现似乎实际上在读取全局内存
不,没有。它读取线程本地内存。该属性很可能是在假定GCC仅在本地(函数内)使用它时添加的,并且永远无法将其用于过程间优化。今天,一些Glibc开发人员质疑属性的正确性,正是因为强大的过程间优化可能会导致错误编译;引用 const属性被指定为断言函数没有 检查除参数之外的任何数据__errno_位置没有 参数,因此每次都必须返回相同的值。 这适用于单线程程序,但不适用于多线程程序 一个。因此,我认为严格来说,它不应该是常数 我们可以说,这神奇地意味着总是在上下文中 指特定的线。忽略GCC不定义线程本身 (特别是像NPTL这样的关于创造 线程),我们仍然可以假设这是有效的,因为在实践中 编译器及其传递不能在中使用的函数之间泄漏知识 一个线程和另一个线程中使用的线程 (
\uuuu errno\u location()
和pthread\u self()
都用\uuuuu属性((const))
标记,并且不接收任何参数)
下面是一个可能与强大的过程间分析混淆的小例子:
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
static void *errno_pointer;
static void *thr(void *unused)
{
if (!errno_pointer || errno_pointer == &errno)
abort();
return 0;
}
int main()
{
errno_pointer = &errno;
pthread_t t;
pthread_create(&t, 0, thr, 0);
pthread_join(t, 0);
}
#包括
#包括
#包括
静态void*errno_指针;
静态空隙*thr(空隙*未使用)
{
如果(!errno_pointer | | errno_pointer==&errno)
中止();
返回0;
}
int main()
{
errno_指针=&errno;
pthread_t;
pthread_创建(&t,0,thr,0);
pthread_-join(t,0);
}
(编译器可以观察到,
errno\u指针
是静态的,它不会逃逸翻译单元,并且其中唯一的存储分配相同的“const”值,该值由\uu errno\u location()
给出,在thr()
中测试)。我在中使用了这个示例,但不幸的是,它没有得到太多的支持。它在给定线程中总是返回相同的值。如果您调用纯函数,更改一个全局变量,然后再次调用它,您可能会得到不同的返回值。如果不在两者之间更改全局变量,并且参数相同,编译器可以假定返回值相同。像pthread\u self()
这样的\uuuu const\uuuu
函数不能发生这种情况,因为返回值不依赖于任何全局变量的值。它将在不同的线程中返回不同的值这一事实与这里发生的事情并不相关。内存实际上不是全局的,它位于%fs
段中,这是一个通常专用于线程本地存储的特殊段。所以它实际上是在访问线程本地内存。@PaulGriffiths:IOW与pure
-ness和const
-ness以及多线程进行交互。很公平,但这并不能解释如何在不读取任何全局内存的情况下实现pthread\u self
pthread_self()
返回调用线程的线程ID,该ID在线程的生命周期内不会更改。它就是这样定义的。如果它真的改变了,那么您的实现就失败了,所以所有的赌注都输掉了。如果你做了一些奇怪的事情来打破它,那么你调用了未定义的行为,所以所有的赌注都是相等的。您个人无法创建一个全局变量来更改该值,而不做一些奇怪的事情,使您无权依赖该保证。即使它确实读取了全局内存,它也是以一种没有明显效果的方式读取的。如果你使用了一个属性,然后违反了允许使用该属性的条件,那么这就很重要了。这几乎就是这里的问题……我知道这是const
和pure
属性的最终目的,但同样,这并不能完全解释为什么const
和pure
属性。。。这是文件吗