contributed by < RZHuangJeff
>
linux2021
This union is the header of simplified implementation of folly::fbstring, for this implementation (mentioned as xs_string
below), it can hold upto 15 characters (exclude null character) on stack with in its 16-byte size header, and upto () characters (include null character) on heap.
Following macros are defined:
MAX_STR_LEN_BITS
: This macro defines how many bits that are used to represent the size of xs_string
.MAX_STR_LEN
: With MAX_STR_LEN_BITS
defined above, the max length of xs_string
can be calculated with following expression: ((1UL << MAX_STR_LEN_BITS) - 1)
.LARGE_STRING_LEN
: This macro defines the minimal size of large string category.With in xs
union, there are three parts of structures.
data
: This array of character makes it easy to access string content while given xs
holds short string on stack.space and flag
: This structure contains two part of members:
space_left
: This field occupied 4 bits, represents the length of string content. Notice that the actual value stored is 15(max length of on-stack xs_string
) minus length of current string content, which will ensure the last byte to be zero (acts as null character) to terminate string content properly. This field is meaningful only for on-stack xs_string
.flags
: There are 4 slots avaliable, only two of them are meaningful. is_ptr
indicates whether string content is stored on heap, is_large_string
indicates whether the length of string content of given xs_string
is greater than LARGE_STRING_LEN
, in order to perform cow feature properly. Notice that such flags will be zero for on-stack xs_string
, which ensures there is a null character for the on-stack xs_string
with full sized string content.heap
: The fields in this structure will be referenced only when is_ptr
is set.
ptr
: A pointer that points to the buffer that stores string content of given xs_string
.size
: This field indicates the length of string content of given xs_string
.capacity
: This field represents the entire size of allocated buffer with in format (buffer size) to reduce space consumption.Since that data
and following two structures are unioned together, we have to ensure that flags in first structure and fields in second structure will not affect to each other, otherwise, unexpected behavior may happend.
The key of such problem is to check the memory organization of xs
, from ISO/IEC 9899:
An implementation may allocate any addressable storage unit large enough to hold a bit-field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.
With in spec of C99, it only mentioned that the successive bit-fields should be packed into adjacent bits if there is enough space remains. But the packing order of succeding bit-fields is implementation-defined. To figure out the actual packing order, following program are designed as experiment:
And is compiled and run under following environment:
The experiment first defines a structure that contains 3 bit-fields with size of 4, 8 and 20 bits, respectively. The field a
and b
are set successively and the content of that structure is printed after each setting, which generates following output:
And the organization of 4-byte integer in little endian byte order is as follows:
With this experiment, we can figure out that the successive bit-fields are packed from low-order to high-order.
After all, following figure shows the memory layout of xs
:
xs_literal_empty
This macro defines an initializer of xs_string
, which will set field space_left
to 15.
xs_new
This function aims to initialize a xs_string
with given string content. Since that xs
string supports string content with length 16 or less (includes null character) to be stored on stack, the length of given string content will be checked to decide where to place the content.
While the length of content is greater than 16, string content will be placed on heap, to do so, the capacity of the buffer should be set up first, it will be set to logarithm of 2 to the closest power of 2 that is greater or equals to the length of string content. After size
and is_ptr
flag are set and the space is allocated, the content is copied to the buffer (line 5 - 10). Otherwise, the content will be placed inside x
, and the space_left
will be set properly (line 12 - 13).
xs_newempty
This function is a wrapper of xs_literal_empty
, that will initialize given xs_string
to empty string.
xs_free
This function tries to free the allocated space on heap while the reference count of that string content shrinks to 0 (line 3 - 4), and x
will be initialized to empty xs_string
before returned (line 5).
xs_allocate_data
This function aims to allocate the space of buffer on heap. There are two kinds of allocation:
LARGE_STRING_LEN
, the space with size bytes is allocated (line 3 - 6).xs_is_ptr & xs_is_large_string
These two functions will access to flag is_ptr
and is_large_string
respectively.
xs_is_ptr
will return whether the string content of given xs_string
is stored on head or not.
The same as xs_is_ptr
, xs_is_large_string
returns whether a given xs_string
is large string or not.
xs_size & xs_data & xs_capacity
These three functions will return different components of given xs_string
.
xs_size
: This function reports the size of string content of given xs_string
, for the content stored on stack is (15 - space_left
), otherwise, the size is stored in field size
.
xs_data
: This function returns the start address of string content. For the string stored on stack, this would be its data
field (line 8 - 9), otherwise, it would be ptr
field, furthermore, there are additional 4 bytes at the begining of buffer, which needs to be omitted (line 12), while given xs_string
is large string.
xs_*_refcnt
These four functions provide an interface to access the reference count of large xs_string
. Since the reference count occupies the begining 4 bytes of allocated buffer, all we need to do to access it is casting field ptr
of given xs_string
to int
.
ilog2
This function aims to calculate logarithm of 2 to n by counting leading zero of n
. Since a 1 in binary representation at position i
indicates and , with fact that logarithm of 2 to numbers between and will land between and , we only need to check where the first 1 appears to calculate logarithm of 2 to n
.
xs_tmp
This macro will create a xs_string
as local variable by calling xs_new
. This macro also performs length checking with macro _Static_assert
, with ISO/IEC 9899:201x:
The constant expression shall be an integer constant expression. If the value of theconstant expression compares unequal to 0, the declaration has no effect. Otherwise, the constraint is violated and the implementation shall produce a diagnostic message that includes the text of the string literal, except that characters not in the basic source character set are not required to appear in the message.
The left operand of a comma operator is evaluated as a void expression; there is a sequence point between its evaluation and that of the right operand. Then the right operand is evaluated; the result has its type and value.
The spec indicates that _Static_assert
expression has no effect while the length of given string content is less or equal to MAX_STR_LEN
, otherwise, the message will be shown and the program will abort. If length checking passes, the result of this macro will be the return value of xs_new
due to the property of comma operator.
xs_grow
This function aims to extend its buffer to be capable of holding string with size len
. It will first check whether len
fits in the size of current buffer, if so, there is no further action taken (line 5 - 6), otherwise, the buffer should be extended. The string that is stored in field data
will be backup first (line 9 - 10), then, the field capability
will be set to fit len
, finally, the space will be allocated (line 15 - 20).
Flag is_ptr
is set at line 12 originally, which will cause the condition of if statement at line 15 always be true, and an error will occur while data is originally stored in field data
, since the external buffer is not allocated yet, while program will try to reallocate it later. This error can be re-produced by following test program:
which will cause segmentation fault.
To fix it, just move the assignment to is_ptr
into the body of else
statement (line 21).
xs_cow_lazy_copy
This function tries to allocate a new space for given xs_string
and detach it from the existed buffer (line 7 - 8) while there are more than 1 references to its string content (line 3 - 4), and the content of allocated buffer will be filled with the content that *data
points to (line 10 - 15).
xs_concat
This function will wrap string
by prefix
and suffix
. Since the content of string
will change, it should be detached from its original content by calling xs_cow_lazy_copy
(line 9). After that, the content of prefix
and suffix
will be copied onto buffer of string
directly while the total length fits in buffer of string
(line 11 - 14). The reason why use memmove
(line 12) instead of memcpy
is that memmove
will take care of memory overlapping, as descriped in Linux manual page. Otherwise, a new xs_string
object is created and is extended to capable of holding modified string content by calling xs_grow
(line 21 - 22), and three parts of string are copied onto it (line 23 - 26), and, the old one is freed (line 27). Finally, the size of string is updated (line 16 - 19, 29).
xs_trim
This function will remove user defined characters that specified in trimset
from the begining and the end of given xs_string
. If there is no character specified in trimset
, there is no further action taken (line 3 -4). Just like xs_concat
, the content of given may change after trimming, it should be detached from its original string content by calling xs_cow_lazy_copy
(line 8 - 9).
To improve the performance of triming, an uint8_t
array with 32 entries that acts as a table with each bit in it represents a character is introduced (line 12). The characters in trimset
is mapped onto that table by macro set_bit
which will set the corresponding bit in table of given character (line 18 - 19). The for
loops around line 20 and 23 perform similar task, they will step through each character form begining and end of string content, until they find a character not matchs characters in trimset
. To discard characters from head and tail, we only need to move pointer to the new begining and shrink its length, respectively (line 26 - 27), and the modified content is shifted forward to the start of buffer (line 33). After setting the null character at the end of string (line 35 - 36) and updating the size of given xs_string
(line 38 - 41), the trimming process is complete.