为什么pthread_self标有属性(const)?

时间:2016-04-26 22:33:54

标签: c gcc pthreads glibc

在Glibc的pthread.h中,pthread_self函数使用const属性声明:

extern pthread_t pthread_self (void) __THROW __attribute__ ((__const__));

在GCC that attribute means中:

  

除了参数之外,许多函数不检查任何值,除了返回值之外没有任何效果。基本上这只是比下面的纯属性稍微严格的类,因为不允许函数读取全局内存。

我想知道那是怎么回事?由于它不需要任何参数,因此pthread_self仅允许始终返回相同的值,这显然不是这种情况。也就是说,我原本期望pthread_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)

这是一个错误还是我没有看到什么?

2 个答案:

答案 0 :(得分:3)

该属性很可能是在假设GCC仅在本地(在函数内)使用它并且永远不能将其用于进程间优化的假设中添加的。今天,一些Glibc开发人员正在质疑属性的正确性,因为强大的过程间优化可能会导致错误编译;引用post by Torvald Riegel to Glibc developers' mailing list

  

const属性被指定为断言该函数没有   检查除参数之外的任何数据。 __errno_location没有   参数,因此每次都必须返回相同的值。   这适用于单线程程序,但不适用于多线程程序   一。因此,我认为严格来说,它不应该是常量。

     

我们可以争辩说,这神奇地意味着永远在上下文中   一个特定的线程。忽略GCC本身并没有定义线程   (特别是像NPTL那样创造一个概念的东西   线程),我们仍然可以认为这是有效的,因为在实践中,   编译器及其传递不能泄漏所用函数的知识   一个线程和另一个线程中使用的另一个线程。

__errno_location()pthread_self()都标有__attribute__((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);
}

(编译器可以观察到errno_pointer是静态的,它不会逃避翻译单元,并且唯一的存储分配给{{1}给出的相同的&#34; const&#34;值},在__errno_location()中测试。我在my email asking to improve documentation of pure/const attributes中使用了这个例子,但不幸的是它并没有太大的吸引力。

答案 1 :(得分:1)

  

我想知道那是怎么回事?

此属性告诉编译器,给定上下文中的 pthread_self将始终返回相同的值。换句话说,下面的两个循环完全等效,允许编译器优化对pthread_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;
  

x86-64上的实现似乎实际上正在读取全局内存

不,它没有。它读取线程本地内存。