Форум программистов, компьютерный форум, киберфорум
C++: WinAPI
Войти
Регистрация
Восстановить пароль
 
Рейтинг 5.00/5: Рейтинг темы: голосов - 5, средняя оценка - 5.00
59 / 31 / 15
Регистрация: 30.05.2009
Сообщений: 224
1

Массив строк, используя кучи Windows

20.04.2014, 09:20. Просмотров 860. Ответов 1
Метки нет (Все метки)

Решил попробовать организовать работу с кучами вместо использования стандартных методов new, delete, malloc, free. Написал класс для создания массива строк. Вот только скорость работы не очень устраивает. Можно ли как-то ускорить работу следующего кода:
ListString.h:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#include <Windows.h>
 
WCHAR error_realloc [] = L"error_Heap :: realloc";
WCHAR error_HeapCreate [] = L"error_HeapCreate";
WCHAR error_HeapAlloc [] = L"error_HeapAlloc";
WCHAR error_HeapFree [] = L"error_HeapFree";
WCHAR error_HeapDestroy [] = L"error_HeapDestroy";
 
class Str
{
    public:
        static int Length ( WCHAR *buff );
        static void SetText ( WCHAR *s1, WCHAR *text );
};
 
int Str :: Length ( WCHAR *buff )
{
    int i = 0;
    while (*buff) {
        buff++;
        i++;
    }
    return i;
}
 
void Str :: SetText ( WCHAR *setStr, WCHAR *getStr )
{
    if ( setStr ) {
        WCHAR *s, *t;
        s = setStr;
        t = getStr;
        while ( *t ) {
            *s = *t;
            s ++;
            t ++;
        }
        *s = NULL;
    }
}
 
class Heap
{
    private:
        static HANDLE s_hHeap; 
        static UINT s_uNumAllocsInHeap;
        // здесь располагаются закрытые данные и функции-члены
    public:
        static void* malloc ( size_t size );
        static void free ( void* p );
        static void* realloc ( void* p, size_t size );
        // здесь располагаются открытые данные и функции-члены
};
 
HANDLE Heap :: s_hHeap = NULL; 
UINT Heap :: s_uNumAllocsInHeap = 0;
 
void* Heap :: realloc ( void* p, size_t size )
{
    return HeapReAlloc ( s_hHeap, HEAP_ZERO_MEMORY, p, size );
}
 
void* Heap :: malloc ( size_t size ) 
{
    if ( s_hHeap == NULL ) {
        // куча не существует, создаем ее 
        s_hHeap = HeapCreate ( HEAP_ZERO_MEMORY, 0, 0 );
        if ( s_hHeap == NULL ) {
            return( NULL );
            MessageBoxW ( 0, error_HeapCreate, NULL, 0 );
        }
    }
    // куча для объектов существует 
    void* p = HeapAlloc( s_hHeap, HEAP_ZERO_MEMORY, size );
    if ( p != NULL ) {
        // память выделена успешно; увеличиваем счетчик объектов в куче
        s_uNumAllocsInHeap++;
    } else MessageBoxW ( 0, error_HeapAlloc, NULL, 0 );
    // возвращаем адрес созданного объекта 
    return( p );
}
 
void Heap :: free ( void* p ) 
{
    if ( p ) {
        if ( HeapFree ( s_hHeap, 0, p ) ) {
            // объект удален успешно 
            s_uNumAllocsInHeap--;
        } else MessageBoxW ( 0, error_HeapFree, NULL, 0 );
        if ( s_uNumAllocsInHeap == 0 ) {
            // если в куче больше нет объектов, уничтожаем ее 
            if ( HeapDestroy( s_hHeap ) ) {
                // описатель кучи приравниваем NULL, чтобы 
                // создать новую кучу при создании нового объекта 
                s_hHeap = NULL;
            } else MessageBoxW ( 0, error_HeapDestroy, NULL, 0 );
        }
    }
}
 
class List
{
    private:
        static UINT count;
        static void updateSizeMassiv ( );
    public:
        static WCHAR **str;
        static void addStr ( WCHAR *s );
        static void setStr ( WCHAR *s, UINT index );
        static void delStr ( UINT index );
        static void free ( );
};
 
UINT List :: count = 0;
WCHAR **List :: str = NULL;
 
void List :: addStr ( WCHAR *s )
{
    if ( count == 0 ) {
        str = ( WCHAR** ) Heap :: malloc ( sizeof( WCHAR* ) );//выделяем память под массив указателей
        count ++;
        if ( s ) {
            int len = Str :: Length ( s );
            *str = ( WCHAR* ) Heap :: malloc ( sizeof( WCHAR ) * ++ len );//выделяем память под строку
            Str :: SetText ( *str, s);
        }
    } else {
        WCHAR **strS;
        strS = ( WCHAR** ) Heap :: realloc ( str, sizeof( WCHAR* ) * ++ count );//изменяем выделенную память под массив указателей
        if ( strS ) {
            str = strS;
            if ( s ) {
                int len = Str :: Length ( s );
                str [ count - 1 ] = ( WCHAR* ) Heap :: malloc ( sizeof( WCHAR ) * ++ len );//выделяем память под строку
                Str :: SetText ( str [ count - 1 ], s);
            }
        } else { 
            MessageBoxW ( 0, error_realloc, NULL, 0 ); 
            count --; 
        }
    }
}
 
void List :: free ( )
{
    for (UINT i = 0; i < count; i ++)
    {
        Heap :: free ( str [ i ] );
        //Heap :: free ( *( str + i ) );
    }
    Heap :: free ( str );
    count = 0;
}
 
void List :: updateSizeMassiv ( )
{
    WCHAR **strS;
    strS = ( WCHAR** ) Heap :: realloc ( str, sizeof( WCHAR* ) * count );//уменьшаем размер массива указателей
    if ( strS ) str = strS;//если удачно изменение размера
        else MessageBoxW ( 0, error_realloc, NULL, 0 );
}
 
void List :: delStr ( UINT index )
{
    if ( index < count ) {//проверка входит ли индекс в список
        Heap :: free ( str [ index ] );//освобождаем память строки
        count --;
        if ( count == index ) updateSizeMassiv ( );//если последний элемент массива 
            else {
                if ( index < count ) {//если элемент где-то внутри массива то цикл по элементам с заменой адреса строки для массива указателей
                    for (UINT i = index; i < count; i ++)
                    {
                        str [ i ] = str [ i + 1 ];
                    }
                    updateSizeMassiv ( );
                }
            }
    }
}
 
void List :: setStr ( WCHAR *s, UINT index )
{
    if ( index < count ) {//проверка входит ли индекс в список
        if ( s ) {//если s неравно NULL
            int len = Str :: Length ( s );
            if ( str [ index ] ) {//если str [ index ] неравно NULL
                WCHAR *strS;
                strS = ( WCHAR* ) Heap :: realloc( str [ index ], sizeof( WCHAR ) * ++ len );//изменение размера памяти под новую строку
                if ( strS ) {
                    str [ index ] = strS;//если удачно заменяем адрес памяти
                    Str :: SetText ( str [ index ], s );
                } else MessageBoxW ( 0, error_realloc, NULL, 0 );
            } else {//если память не выделялась под строку
                str [ index ] = ( WCHAR* ) Heap :: malloc ( sizeof( WCHAR ) * ++ len );//выделяем память под строку
                Str :: SetText ( str [ index ], s );
            }
        } else {
            Heap :: free ( str [ index ] );//освобождаем память строки
            str [ index ] = NULL;
        }
    }
}
программа:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <Windows.h>
#include "ListString.h"
 
// точка входа
void EntryPoint ( void )
{
    MessageBoxW ( 0, L"начало" , NULL, 0 );
    List :: addStr ( NULL );
    List :: addStr ( L"1279854745" );
    List :: addStr ( L"fhtjdrjdr785" );
    List :: addStr ( L"43459dh78977773101" );
    for (UINT i = 0; i < 10000; i++)
    {
        List :: addStr ( L"fhtjdrjdr785" );
    }
    MessageBoxW ( 0, List :: str [ 0 ], NULL, 0 );
    List :: setStr ( L"125", 0 );
    MessageBoxW ( 0, List :: str [ 0 ], NULL, 0 );
    /*for (UINT i = 0; i < 700; i ++)
    {
        List :: delStr ( 2 );
    }*/
    List :: delStr ( 2 );
    MessageBoxW ( 0, List :: str [ 2 ], NULL, 0 );
    List :: free( );
    MessageBoxW ( 0, L"конец" , NULL, 0 );
    ExitProcess( 0 );
}
Знаю что такое можно создать с использованием вектора, но я просто хочу написать простую программу без платформы Microsoft.net. Заранее спасибо.

Добавлено через 20 часов 20 минут
Похоже у меня есть ошибка в процедуре void List :: setStr ( WCHAR *s, UINT index ). В 197, 198 строках, т.к. пытаюсь записать NULL в освобожденную память. Похоже надо просто задать изменение размера, через Heap::realoc, а потом записывать NULL. Возможно есть ошибка и тут: void List :: delStr ( UINT index ). Буду разбираться дальше.
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
20.04.2014, 09:20
Ответы с готовыми решениями:

Используя функцию копирования строк, организуйте конкатенацію и копирование строк в четвертый массив, содержащий полные имена
Пожалуйста с этим заданием. Написать программу, которая описывает четыре массива. Три первых...

Из исходной таблицы в n строк и 6 столбцов нужно сделать таблицу-результат из кучи строк и 6 столбцов
Добрый вечер, учусь в универcитете, начал изучать макросы и подвернулась &quot;интересная&quot; задача -...

Получать различные начала кучи при создании кучи внутри цикла
Можно ли как-то такое провернуть, чтобы на каждой итерации цикла получались различные адреса...

Посимвольная обработка огромной кучи текстовых строк
проблема в следущем: имеется большая база текстовых данных в екселе.. нужно убрать из нее все...

1
59 / 31 / 15
Регистрация: 30.05.2009
Сообщений: 224
25.04.2014, 18:55  [ТС] 2
Сейчас вроде должно быть всё правильно, хотя кто знает. Если найдёте ошибки в коде то не забудьте мне написать. Заранее спасибо.
Переписал вот так:
Файл ListString.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#include <Windows.h>
 
WCHAR error_realloc [] = L"error_Heap :: realloc";
WCHAR error_HeapCreate [] = L"error_HeapCreate";
WCHAR error_HeapAlloc [] = L"error_HeapAlloc";
WCHAR error_HeapFree [] = L"error_HeapFree";
WCHAR error_HeapDestroy [] = L"error_HeapDestroy";
 
class Str
{
    public:
        static int Length ( WCHAR *buff );
        static void SetText ( WCHAR *s1, WCHAR *text );
};
 
int Str :: Length ( WCHAR *buff )
{
    int i = 0;
    while (*buff) {
        buff++;
        i++;
    }
    return i;
}
 
void Str :: SetText ( WCHAR *setStr, WCHAR *getStr )
{
    if ( setStr ) {
        WCHAR *s, *t;
        s = setStr;
        t = getStr;
        while ( *t ) {
            *s = *t;
            s ++;
            t ++;
        }
        *s = NULL;
    }
}
 
class Heap
{
    private:
        static HANDLE s_hHeap; 
        static UINT s_uNumAllocsInHeap;
        // здесь располагаются закрытые данные и функции-члены
    public:
        static void* malloc ( size_t size );
        static void free ( void* p );
        static void* realloc ( void* p, size_t size );
        // здесь располагаются открытые данные и функции-члены
};
 
HANDLE Heap :: s_hHeap = NULL; 
UINT Heap :: s_uNumAllocsInHeap = 0;
 
void* Heap :: realloc ( void* p, size_t size )
{
    return HeapReAlloc ( s_hHeap, HEAP_ZERO_MEMORY, p, size );
}
 
void* Heap :: malloc ( size_t size ) 
{
    if ( !s_hHeap ) {//s_hHeap == NULL
        // куча не существует, создаем ее 
        s_hHeap = HeapCreate ( HEAP_ZERO_MEMORY, 0, 0 );
        if ( !s_hHeap ) {//s_hHeap == NULL
            MessageBoxW ( 0, error_HeapCreate, NULL, 0 );
            return( NULL );
        }
    }
    // куча для объектов существует 
    void* p = HeapAlloc( s_hHeap, HEAP_ZERO_MEMORY, size );
    if ( p ) {//p != NULL
        // память выделена успешно; увеличиваем счетчик объектов в куче
        s_uNumAllocsInHeap++;
    } else MessageBoxW ( 0, error_HeapAlloc, NULL, 0 );
    // возвращаем адрес созданного объекта 
    return ( p );
}
 
void Heap :: free ( void* p ) 
{
    if ( p ) {
        if ( HeapFree ( s_hHeap, 0, p ) ) {
            // объект удален успешно 
            s_uNumAllocsInHeap--;
        } else MessageBoxW ( 0, error_HeapFree, NULL, 0 );
        if ( s_uNumAllocsInHeap == 0 ) {
            // если в куче больше нет объектов, уничтожаем ее 
            if ( HeapDestroy( s_hHeap ) ) {
                // описатель кучи приравниваем NULL, чтобы 
                // создать новую кучу при создании нового объекта 
                s_hHeap = NULL;
            } else MessageBoxW ( 0, error_HeapDestroy, NULL, 0 );
        }
    }
}
 
static struct TListStructur
{
    WCHAR **List;
    UINT count;
};
 
class List
{
    private:
        static void updateSizeMassiv ( TListStructur *tList );
    public:
        static void addStr ( TListStructur *tList, WCHAR *s );
        static void setStr ( TListStructur *tList, WCHAR *s, UINT index );
        static void delStr ( TListStructur *tList, UINT index );
        static void free ( TListStructur *tList );
};
 
void List :: addStr ( TListStructur *tList, WCHAR *s )
{
    if ( ( *tList ) . count == 0 ) {
        ( *tList ) . List = ( WCHAR** ) Heap :: malloc ( sizeof( WCHAR* ) );//выделяем память под массив указателей
        ( *tList ) . count ++;
        if ( s ) {
            int len = Str :: Length ( s );
            ( *tList ) . List [ 0 ] = ( WCHAR* ) Heap :: malloc ( sizeof( WCHAR ) * ++ len );//выделяем память под строку
            Str :: SetText ( ( *tList ) . List [ 0 ], s);
        } else ( *tList ) . List [ 0 ] = NULL;
    } else {
        WCHAR **strS;
        strS = ( WCHAR** ) Heap :: realloc ( ( *tList ) . List, sizeof( WCHAR* ) * ++ ( *tList ) . count );//изменяем выделенную память под массив указателей
        if ( strS ) {
            ( *tList ) . List = strS;
            if ( s ) {
                int len = Str :: Length ( s );
                ( *tList ) . List [ ( *tList ) . count - 1 ] = ( WCHAR* ) Heap :: malloc ( sizeof( WCHAR ) * ++ len );//выделяем память под строку
                Str :: SetText ( ( *tList ) . List [ ( *tList ) . count - 1 ], s);
            } else ( *tList ) . List [ ( *tList ) . count - 1 ] = NULL;
        } else { 
            MessageBoxW ( 0, error_realloc, NULL, 0 ); 
            ( *tList ) . count --; 
        }
    }
}
 
void List :: free ( TListStructur *tList )
{
    for (UINT i = 0; i < ( *tList ) . count; i ++)
    {
        Heap :: free ( ( *tList ) . List [ i ] );
    }
    Heap :: free ( ( *tList ) . List );
    ( *tList ) . count = 0;
    ( *tList ) .List = NULL;
}
 
void List :: updateSizeMassiv ( TListStructur *tList)
{
    WCHAR **strS;
    strS = ( WCHAR** ) Heap :: realloc ( ( *tList ) . List, sizeof( WCHAR* ) * ( *tList ) . count );//уменьшаем размер массива указателей
    if ( strS ) ( *tList ) . List = strS;//если удачно изменение размера
        else MessageBoxW ( 0, error_realloc, NULL, 0 );
}
 
void List :: delStr ( TListStructur *tList, UINT index )
{
    if ( index < ( *tList ) . count ) {//проверка входит ли индекс в список
        Heap :: free ( ( *tList ) . List [ index ] );//освобождаем память строки
        ( *tList ) . List [ index ] = NULL;
        ( *tList ) . count --;
        if ( ( *tList ) . count == index ) updateSizeMassiv ( tList );//если последний элемент массива 
            else {
                if ( index < ( *tList ) . count ) {//если элемент где-то внутри массива то цикл по элементам с заменой адреса строки для массива указателей
                    for (UINT i = index; i < ( *tList ) . count; i ++)
                    {
                        ( *tList ) . List [ i ] = ( *tList ) . List [ i + 1 ];
                    }
                    updateSizeMassiv ( tList );
                }
            }
    }
}
 
void List :: setStr ( TListStructur *tList, WCHAR *s, UINT index )
{
    if ( index < ( *tList ) . count ) {//проверка входит ли индекс в список
        if ( s ) {//если s неравно NULL
            int len = Str :: Length ( s );
            if ( ( *tList ) . List [ index ] ) {//если str [ index ] неравно NULL
                WCHAR *strS;
                strS = ( WCHAR* ) Heap :: realloc( ( *tList ) . List [ index ], sizeof( WCHAR ) * ++ len );//изменение размера памяти под новую строку
                if ( strS ) {
                    ( *tList ) . List [ index ] = strS;//если удачно заменяем адрес памяти
                    Str :: SetText ( ( *tList ) . List [ index ], s );
                } else MessageBoxW ( 0, error_realloc, NULL, 0 );
            } else {//если память не выделялась под строку
                ( *tList ) . List [ index ] = ( WCHAR* ) Heap :: malloc ( sizeof( WCHAR ) * ++ len );//выделяем память под строку
                Str :: SetText ( ( *tList ) . List [ index ], s );
            }
        } else {
            Heap :: free ( ( *tList ) . List [ index ] );//освобождаем память строки
            ( *tList ) . List [ index ] = NULL;
        }
    }
}
программа:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <Windows.h>
#include "ListString.h"
 
// точка входа
void EntryPoint ( void )
{
    TListStructur list1, list2;//для двух списков
    list1.count = 0;//заполняем структуру для правильной работы
    list1.List = NULL;
    list2.count = 0;//заполняем структуру для правильной работы
    list2.List = NULL;
    MessageBoxW ( 0, L"начало" , NULL, 0 );
    List :: addStr ( &list2,  L"stroka1" );//добавляем строку для второго списка
    List :: addStr ( &list2,  L"stroka2" );//добавляем строку для второго списка
    List :: addStr ( &list2,  L"stroka3" );//добавляем строку для второго списка
    List :: addStr ( &list1,  NULL );//добавляем строку для первого списка
    List :: addStr ( &list1, L"1279854745" );//добавляем строку для первого списка
    List :: addStr ( &list1, L"fhtjdrjdr785" );//добавляем строку для первого списка
    List :: addStr ( &list1, L"43459dh78977773101" );//добавляем строку для первого списка
    for (UINT i = 0; i < 10000; i++)
    {
        List :: addStr ( &list1, L"fhtjdrjdr785" );//добавляем строку для первого списка
    }
    MessageBoxW ( 0, list1 . List [ 1 ], NULL, 0 );
    List :: setStr ( &list1, L"125", 1 );//изменяем строку 2 из первого списка
    MessageBoxW ( 0, list1 . List [ 1 ], NULL, 0 );
    for (UINT i = 0; i < 700; i ++)
    {
        List :: delStr ( &list1, 2 );//удаляем строку 3 из первого списка
    }
    List :: delStr ( &list1, 1 );//удаляем строку 2 из первого списка
    MessageBoxW ( 0, list1 . List [ 1 ], NULL, 0 );
    MessageBoxW ( 0, list2 . List [ 0 ], NULL, 0 );
    MessageBoxW ( 0, list2 . List [ 1 ], NULL, 0 );
    MessageBoxW ( 0, list2 . List [ 2 ], NULL, 0 );
    List :: free( &list1 );//очистка памяти первого списка
    List :: free( &list2 );//очистка памяти второго списка
    MessageBoxW ( 0, L"конец" , NULL, 0 );
    ExitProcess( 0 );
}
Добавлено через 23 часа 1 минуту
Уменьшил немного код в файле ListString.h, изменил процедуру добавления элемента в список:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void List :: addStr ( TListStructur *tList, WCHAR *s )
{
    WCHAR **strS;
    strS =  ( ( ( *tList ) . count ) ? ( WCHAR** ) Heap :: realloc ( ( *tList ) . List, sizeof( WCHAR* ) * ++ ( *tList ) . count ) : ( WCHAR** ) Heap :: malloc ( sizeof( WCHAR* ) * ++ ( *tList ) . count ) );//изменяем выделенную память под массив указателей
    if ( strS ) {
        ( *tList ) . List = strS;
        if ( s ) {
            int len = Str :: Length ( s );
            ( *tList ) . List [ ( *tList ) . count - 1 ] = ( WCHAR* ) Heap :: malloc ( sizeof( WCHAR ) * ++ len );//выделяем память под строку
            Str :: SetText ( ( *tList ) . List [ ( *tList ) . count - 1 ], s);
        } else ( *tList ) . List [ ( *tList ) . count - 1 ] = NULL;
    } else { 
        MessageBoxW ( 0, error_realloc, NULL, 0 ); 
        ( *tList ) . count --; 
    }
}
0
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
25.04.2014, 18:55

Заказываю контрольные, курсовые, дипломные и любые другие студенческие работы здесь.

Heapsort (метод кучи, пирамидальная сортировка) для строк
Доброго всем времени суток, может ли кто-то поделиться опытом сортировки по алфавиту методом...

Используя указатель рассортировать массив строк в алфавитном порядке
Доброй ночи, ребят! необходимо используя указатель рассортировать массив строк в алфавитном...

Используя указатель рассортировать массив строк в алфавитном порядке
Используя указатель рассортировать массив строк в алфавитном порядке. Может знает кто?

Разбить камни на две кучи так, чтобы вес одной кучи не превышал веса другой более критической массы
Здраствуйте,уважаемые програмисты!Проблемы с задачкой Условие:Есть куча камней,каждый камень имеет...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
2
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2021, vBulletin Solutions, Inc.