Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qarraydata.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// Copyright (C) 2016 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include <QtCore/qarraydata.h>
6#include <QtCore/private/qnumeric_p.h>
7#include <QtCore/private/qtools_p.h>
8#include <QtCore/qmath.h>
9
10#include <QtCore/qbytearray.h> // QBA::value_type
11#include <QtCore/qstring.h> // QString::value_type
12
13#include <stdlib.h>
14
15QT_BEGIN_NAMESPACE
16
17/*
18 * This pair of functions is declared in qtools_p.h and is used by the Qt
19 * containers to allocate memory and grow the memory block during append
20 * operations.
21 *
22 * They take qsizetype parameters and return qsizetype so they will change sizes
23 * according to the pointer width. However, knowing Qt containers store the
24 * container size and element indexes in ints, these functions never return a
25 * size larger than INT_MAX. This is done by casting the element count and
26 * memory block size to int in several comparisons: the check for negative is
27 * very fast on most platforms as the code only needs to check the sign bit.
28 *
29 * These functions return SIZE_MAX on overflow, which can be passed to malloc()
30 * and will surely cause a NULL return (there's no way you can allocate a
31 * memory block the size of your entire VM space).
32 */
33
34/*!
35 \internal
36 \since 5.7
37
38 Returns the memory block size for a container containing \a elementCount
39 elements, each of \a elementSize bytes, plus a header of \a headerSize
40 bytes. That is, this function returns \c
41 {elementCount * elementSize + headerSize}
42
43 but unlike the simple calculation, it checks for overflows during the
44 multiplication and the addition.
45
46 Both \a elementCount and \a headerSize can be zero, but \a elementSize
47 cannot.
48
49 This function returns -1 on overflow or if the memory block size
50 would not fit a qsizetype.
51*/
52qsizetype qCalculateBlockSize(qsizetype elementCount, qsizetype elementSize, qsizetype headerSize) noexcept
53{
54 Q_ASSERT(elementSize);
55
56 size_t bytes;
57 if (Q_UNLIKELY(qMulOverflow(size_t(elementSize), size_t(elementCount), &bytes)) ||
58 Q_UNLIKELY(qAddOverflow(bytes, size_t(headerSize), &bytes)))
59 return -1;
60 if (Q_UNLIKELY(qsizetype(bytes) < 0))
61 return -1;
62
63 return qsizetype(bytes);
64}
65
66/*!
67 \internal
68 \since 5.7
69
70 Returns the memory block size and the number of elements that will fit in
71 that block for a container containing \a elementCount elements, each of \a
72 elementSize bytes, plus a header of \a headerSize bytes. This function
73 assumes the container will grow and pre-allocates a growth factor.
74
75 Both \a elementCount and \a headerSize can be zero, but \a elementSize
76 cannot.
77
78 This function returns -1 on overflow or if the memory block size
79 would not fit a qsizetype.
80
81 \note The memory block may contain up to \a elementSize - 1 bytes more than
82 needed.
83*/
85qCalculateGrowingBlockSize(qsizetype elementCount, qsizetype elementSize, qsizetype headerSize) noexcept
86{
87 CalculateGrowingBlockSizeResult result = {
88 qsizetype(-1), qsizetype(-1)
89 };
90
91 qsizetype bytes = qCalculateBlockSize(elementCount, elementSize, headerSize);
92 if (bytes < 0)
93 return result;
94
95 size_t morebytes = static_cast<size_t>(qNextPowerOfTwo(quint64(bytes)));
96 if (Q_UNLIKELY(qsizetype(morebytes) < 0)) {
97 // grow by half the difference between bytes and morebytes
98 // this slows the growth and avoids trying to allocate exactly
99 // 2G of memory (on 32bit), something that many OSes can't deliver
100 bytes += (morebytes - bytes) / 2;
101 } else {
102 bytes = qsizetype(morebytes);
103 }
104
105 result.elementCount = (bytes - headerSize) / elementSize;
106 result.size = result.elementCount * elementSize + headerSize;
107 return result;
108}
109
110/*
111 Calculate the byte size for a block of \a capacity objects of size \a
112 objectSize, with a header of size \a headerSize. If the \a option is
113 QArrayData::Grow, the capacity itself adjusted up, preallocating room for
114 more elements to be added later; otherwise, it is an exact calculation.
115
116 Returns a structure containing the size in bytes and elements available.
117*/
119calculateBlockSize(qsizetype capacity, qsizetype objectSize, qsizetype headerSize, QArrayData::AllocationOption option)
120{
121 // Adjust the header size up to account for the trailing null for QString
122 // and QByteArray. This is not checked for overflow because headers sizes
123 // should not be anywhere near the overflow limit.
124 constexpr qsizetype FooterSize = qMax(sizeof(QString::value_type), sizeof(QByteArray::value_type));
125 if (objectSize <= FooterSize)
126 headerSize += FooterSize;
127
128 // allocSize = objectSize * capacity + headerSize, but checked for overflow
129 // plus padded to grow in size
130 if (option == QArrayData::Grow) {
131 return qCalculateGrowingBlockSize(capacity, objectSize, headerSize);
132 } else {
133 return { qCalculateBlockSize(capacity, objectSize, headerSize), capacity };
134 }
135}
136
137namespace {
138struct AllocationResult {
139 void *data;
140 QArrayData *header;
141};
142}
143using QtPrivate::AlignedQArrayData;
144
145static inline AllocationResult
146allocateHelper(qsizetype objectSize, qsizetype alignment, qsizetype capacity,
147 QArrayData::AllocationOption option) noexcept
148{
149 if (capacity == 0)
150 return {};
151
152 qsizetype headerSize = sizeof(AlignedQArrayData);
153 const qsizetype headerAlignment = alignof(AlignedQArrayData);
154
155 if (alignment > headerAlignment) {
156 // Allocate extra (alignment - Q_ALIGNOF(AlignedQArrayData)) padding
157 // bytes so we can properly align the data array. This assumes malloc is
158 // able to provide appropriate alignment for the header -- as it should!
159 // Effectively, we allocate one QTypedArrayData<T>::AlignmentDummy.
160 headerSize += alignment - headerAlignment;
161 }
162 Q_ASSERT(headerSize > 0);
163
164 auto blockSize = calculateBlockSize(capacity, objectSize, headerSize, option);
165 capacity = blockSize.elementCount;
166 qsizetype allocSize = blockSize.size;
167 if (Q_UNLIKELY(allocSize < 0)) // handle overflow. cannot allocate reliably
168 return {};
169
170 void *data = nullptr;
171 QArrayData *header = static_cast<QArrayData *>(::malloc(size_t(allocSize)));
172 if (Q_LIKELY(header)) {
173 header->ref_.storeRelaxed(1);
174 header->flags = {};
175 // find where offset should point to so that data() is aligned to alignment bytes
176 data = QTypedArrayData<void>::dataStart(header, alignment);
177 header->alloc = capacity;
178 }
179
180 return { data, header };
181}
182
183// Generic size and alignment allocation function
184void *QArrayData::allocate(QArrayData **dptr, qsizetype objectSize, qsizetype alignment,
185 qsizetype capacity, AllocationOption option) noexcept
186{
187 Q_ASSERT(dptr);
188 // Alignment is a power of two
189 Q_ASSERT(alignment >= qsizetype(alignof(QArrayData))
190 && !(alignment & (alignment - 1)));
191
192 auto r = allocateHelper(objectSize, alignment, capacity, option);
193 *dptr = r.header;
194 return r.data;
195}
196
197// Fixed size and alignment allocation functions
198void *QArrayData::allocate1(QArrayData **dptr, qsizetype capacity, AllocationOption option) noexcept
199{
200 Q_ASSERT(dptr);
201
202 auto r = allocateHelper(1, alignof(AlignedQArrayData), capacity, option);
203 *dptr = r.header;
204 return r.data;
205}
206
207void *QArrayData::allocate2(QArrayData **dptr, qsizetype capacity, AllocationOption option) noexcept
208{
209 Q_ASSERT(dptr);
210
211 auto r = allocateHelper(2, alignof(AlignedQArrayData), capacity, option);
212 *dptr = r.header;
213 return r.data;
214}
215
216std::pair<QArrayData *, void *>
217QArrayData::reallocateUnaligned(QArrayData *data, void *dataPointer,
218 qsizetype objectSize, qsizetype capacity, AllocationOption option) noexcept
219{
220 Q_ASSERT(!data || !data->isShared());
221
222 const qsizetype headerSize = sizeof(AlignedQArrayData);
223 auto r = calculateBlockSize(capacity, objectSize, headerSize, option);
224 qsizetype allocSize = r.size;
225 capacity = r.elementCount;
226 if (Q_UNLIKELY(allocSize < 0))
227 return {};
228
229 const qptrdiff offset = dataPointer
230 ? reinterpret_cast<char *>(dataPointer) - reinterpret_cast<char *>(data)
231 : headerSize;
232 Q_ASSERT(offset > 0);
233 Q_ASSERT(offset <= allocSize); // equals when all free space is at the beginning
234
235 QArrayData *header = static_cast<QArrayData *>(::realloc(data, size_t(allocSize)));
236 if (header) {
237 header->alloc = capacity;
238 dataPointer = reinterpret_cast<char *>(header) + offset;
239 } else {
240 dataPointer = nullptr;
241 }
242 return {header, dataPointer};
243}
244
245void QArrayData::deallocate(QArrayData *data, qsizetype objectSize,
246 qsizetype alignment) noexcept
247{
248 // Alignment is a power of two
249 Q_ASSERT(alignment >= qsizetype(alignof(QArrayData))
250 && !(alignment & (alignment - 1)));
251 Q_UNUSED(objectSize);
252 Q_UNUSED(alignment);
253
254 ::free(data);
255}
256
257QT_END_NAMESPACE
static CalculateGrowingBlockSizeResult calculateBlockSize(qsizetype capacity, qsizetype objectSize, qsizetype headerSize, QArrayData::AllocationOption option)
CalculateGrowingBlockSizeResult qCalculateGrowingBlockSize(qsizetype elementCount, qsizetype elementSize, qsizetype headerSize) noexcept
static AllocationResult allocateHelper(qsizetype objectSize, qsizetype alignment, qsizetype capacity, QArrayData::AllocationOption option) noexcept