淺析C語言中的rand函數和srand函數(二)


盡管ISO C99使用了非常簡單的並且具備移植性的樣例描述了rand函數和srand函數的實現。但是在具體的C語言函數庫的實現上,由於考慮到運行效率以及線程安全,代碼就稍微多了一些。

這里以glibc 2.18為例。

在stdlib目錄下,我們找到rand.c,內容如下:

1 /* Return a random integer between 0 and RAND_MAX.  */
2 int
3 rand (void)
4 {
5   return (int) __random ();
6 }

 

在同目錄下的random.c,我們找到__random函數,內容如下:

 1 long int
 2 __random (void)
 3 {
 4   int32_t retval;
 5 
 6   __libc_lock_lock (lock);
 7 
 8   (void) __random_r (&unsafe_state, &retval);
 9 
10   __libc_lock_unlock (lock);
11 
12   return retval;
13 }

 

這個函數調用__random_r函數,傳入兩個參數&unsafe_state和&retval,返回retval。所以我們可以確定retval是即將生成的偽隨機數,而unsafe_state是什么呢?

在random.c中,我們找到unsafe_state的定義:

 1 static struct random_data unsafe_state =
 2   {
 3     .fptr = &randtbl[SEP_3 + 1],
 4     .rptr = &randtbl[1],
 5 
 6     .state = &randtbl[1],
 7 
 8     .rand_type = TYPE_3,
 9     .rand_deg = DEG_3,
10     .rand_sep = SEP_3,
11 
12     .end_ptr = &randtbl[sizeof (randtbl) / sizeof (randtbl[0])]
13 };

 

unsafe_state是一個全局的靜態變量,類型為random_data的結構體。我們查看stdlib.h,找到struct random_data的定義,發現它其實就是我們一直所說的偽隨機數發生器的“種子”,在glibc的實現中,由於在多線程的情況下,如果函數使用一個靜態變量,則這個函數不具備有“可重入”性,就是在多線程調用的情況下會發生意想不到的情形。所以glibc對這種情況作出了修正,保證了rand函數的“可重入性”。首先我們來看random_data的定義:

 1 /* Reentrant versions of the `random' family of functions.
 2    These functions all use the following data structure to contain
 3    state, rather than global state variables.  */
 4 
 5 struct random_data
 6   {
 7     int32_t *fptr;        /* Front pointer.  */
 8     int32_t *rptr;        /* Rear pointer.  */
 9     int32_t *state;        /* Array of state values.  */
10     int rand_type;        /* Type of random number generator.  */
11     int rand_deg;        /* Degree of random number generator.  */
12     int rand_sep;        /* Distance between front and rear.  */
13     int32_t *end_ptr;        /* Pointer behind state table.  */
14   };

 

那么glibc是如何保證函數的可重入性的呢?其實就是__random函數中的兩行代碼__libc_lock_lock (lock)和__libc_lock_unlock (lock),這個lock保證了線程在訪問&unsafe_state資源的互斥性,從而保證了函數的可重入性。那么這個lock的機制是從何而來的呢?在random.c文件中我們可以讀到lock的初始化語句:

1 /* POSIX.1c requires that there is mutual exclusion for the `rand' and
2    `srand' functions to prevent concurrent calls from modifying common
3    data.  */
4 __libc_lock_define_initialized (static, lock)

 

初始化鎖(__libc_lock_define_initialized)、加鎖(__libc_lock_lock)、解鎖(__libc_lock_unlock)的操作屬於宏,我們可以在bits目錄下的libc-lock.h中找到宏的定義(這里說明一下,在我下載的glic源碼中的該文件是stub version,缺少具體的定義,僅有宏名稱。我在unbuntu12.04上找到了相應的bits目錄下的libc-lock.h,屬於NPTL version,有宏的定義):

 1 typedef pthread_mutex_t __libc_lock_t;
 2 
 3 #  define __libc_lock_define_initialized(CLASS,NAME) \
 4   CLASS __libc_lock_t NAME;
 5 
 6 # define __libc_lock_lock(NAME) \
 7   ({ lll_lock (NAME, LLL_PRIVATE); 0; })
 8 
 9 # define __libc_lock_unlock(NAME) \
10   lll_unlock (NAME, LLL_PRIVATE)

 

而lll_lock和lll_unlock屬於底層的對互斥鎖進行操作的宏,這里不深究。

在保證了函數的“可重入性”之后,rand函數調用鏈條上的最后一環就是__random_r這個函數(在random_r.c中),它真正進行對unsafe_state和retval的操作,產生一個偽隨機數,並且對“種子”進行更新。

 1 int
 2 __random_r (buf, result)
 3      struct random_data *buf;
 4      int32_t *result;
 5 {
 6   int32_t *state;
 7 
 8   if (buf == NULL || result == NULL)
 9     goto fail;
10 
11   state = buf->state;
12 
13   if (buf->rand_type == TYPE_0)
14     {
15       int32_t val = state[0];
16       val = ((state[0] * 1103515245) + 12345) & 0x7fffffff;
17       state[0] = val;
18       *result = val;
19     }
20   else
21     {
22       int32_t *fptr = buf->fptr;
23       int32_t *rptr = buf->rptr;
24       int32_t *end_ptr = buf->end_ptr;
25       int32_t val;
26 
27       val = *fptr += *rptr;
28       /* Chucking least random bit.  */
29       *result = (val >> 1) & 0x7fffffff;
30       ++fptr;
31       if (fptr >= end_ptr)
32     {
33       fptr = state;
34       ++rptr;
35     }
36       else
37     {
38       ++rptr;
39       if (rptr >= end_ptr)
40         rptr = state;
41     }
42       buf->fptr = fptr;
43       buf->rptr = rptr;
44     }
45   return 0;
46 
47  fail:
48   __set_errno (EINVAL);
49   return -1;
50 }
__random_r函數的定義

 

在看完了rand函數之后,讓我們來看看srand函數。在目錄中,我們找不到srand.c這樣的文件,但是在random.c中,我們可以看到:

 1 weak_alias (__srandom, srandom) 

 1 weak_alias (__srandom, srand) 

這兩行代碼的意思就是為__srandom這個符號設置一個弱符號的別名。什么是弱符號,這里不深究。大致的意思就是如果你在其他的文件中定義了srand函數和srandom函數,你可以放心使用你定義的函數,而不必擔心被這里的弱符號別名所影響。weak_alias的定義在libc-symbols.h當中:

1 /* Define ALIASNAME as a weak alias for NAME.
2    If weak aliases are not available, this defines a strong alias.  */
3 # define weak_alias(name, aliasname) _weak_alias (name, aliasname)
4 # define _weak_alias(name, aliasname) \
5   extern __typeof (name) aliasname __attribute__ ((weak, alias (#name)));

 

所以要看srand函數,就看__srandom函數,這個函數接收一個x,在編程當中我們調用srand((unsigned int)time(NULL)),所以x就是當前的時間距離1970年1月1日0時的秒數。函數如下:

1 void
2 __srandom (x)
3      unsigned int x;
4 {
5   __libc_lock_lock (lock);
6   (void) __srandom_r (x, &unsafe_state);
7   __libc_lock_unlock (lock);
8 }

 

我們在random_r.c中找到__srandom_r函數,這個函數根據傳入的x來改變全局靜態變量unsafe_state的狀態,就是改變了“種子”,所以能夠使偽隨機數發生器根據這個“種子”來產生偽隨機數序列。函數的實現如下:

 1 int
 2 __srandom_r (seed, buf)
 3      unsigned int seed;
 4      struct random_data *buf;
 5 {
 6   int type;
 7   int32_t *state;
 8   long int i;
 9   int32_t word;
10   int32_t *dst;
11   int kc;
12 
13   if (buf == NULL)
14     goto fail;
15   type = buf->rand_type;
16   if ((unsigned int) type >= MAX_TYPES)
17     goto fail;
18 
19   state = buf->state;
20   /* We must make sure the seed is not 0.  Take arbitrarily 1 in this case.  */
21   if (seed == 0)
22     seed = 1;
23   state[0] = seed;
24   if (type == TYPE_0)
25     goto done;
26 
27   dst = state;
28   word = seed;
29   kc = buf->rand_deg;
30   for (i = 1; i < kc; ++i)
31     {
32       /* This does:
33        state[i] = (16807 * state[i - 1]) % 2147483647;
34      but avoids overflowing 31 bits.  */
35       long int hi = word / 127773;
36       long int lo = word % 127773;
37       word = 16807 * lo - 2836 * hi;
38       if (word < 0)
39     word += 2147483647;
40       *++dst = word;
41     }
42 
43   buf->fptr = &state[buf->rand_sep];
44   buf->rptr = &state[0];
45   kc *= 10;
46   while (--kc >= 0)
47     {
48       int32_t discard;
49       (void) __random_r (buf, &discard);
50     }
51 
52  done:
53   return 0;
54 
55  fail:
56   return -1;
57 }
__srandom_r函數的定義

 

 

至此,我們應該可以說自己對glibc中rand函數和srand函數的實現有了初步的認識。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM