Now within FFmpeg, there's a library called libavutil. libavutil contains a number of general utilities which are used by various libraries within the project itself, including libavformat. Memory allocation undoubtedly plays a big part in these kinds of libraries and the memory allocator used in libavformat is actually included from libavutil. So I'm tackling this part of the library first.
So here is the the bread and butter of the memory allocator. Immediately, you can notice that there's some conditional compilation going on.
- CONFIG_MEMALIGN_HACK
- HAVE_POSIX_MEMALIGN
- HAVE_MEMALIGN
CONFIG_MEMALIGN_HACK is only used when the OS doesn't have one of the two supported memalign functions. So in this scenario, the memory alignment is done manually. HAVE_MEMALIGN and HAVE_POSIX_MEMALIGN are similar. HAVE_POSIX_MEMALIGN takes precedence over HAVE_MEMALIGN since posix_memalign() is more compliant on systems that have certain alignment restrictions. For all intents and purposes, posix_memalign() and memalign() can just be thought of as two functions that return pointers to blocks of memory which are aligned on the boundaries specified.
Now with that aside, we can take a closer look at the code.
Here we see that it's doing a check to make sure that the size is at least 32 bytes less than the maximum size of data allowed to be allocated. The first question one should ask when seeing this is, why 32 bytes? The answer is memory alignment. ALIGN is defined as either 16 or 32 depending on whether or not the processor supports AVX instructions. Here, it seems, they got lazy and instead of just checking to see whether it was 16 or 32, they just assumed the larger. So if the size requested was too large, it just returns NULL and that's the end of it. However, if the size wasn't too large, things become more interesting. Now the memory is allocated and aligned. If HAVE_MEMALIGN or HAVE_POSIX_MEMALIGN is defined then the corresponding function is called. However, when CONFIG_MEMALIGN_HACK is defined, some interesting code is run.
The first thing this section of code does is that it makes a call to malloc() and allocates size+ALIGN bytes of memory. From our previous observations it should be obvious that the extra bytes allocated is so that alignment can take place. Now if the allocation fails, ptr is set to NULL and the value of ptr is returned.
Now let's look at what happens if the allocation was successful. It sets diff to the result of an expression containing ptr and ALIGN that involves some bitwise operations. The first part of the expression is ((-(long)ptr - 1). Now the '-' operator at the front tells us that it's computing the negative equivalent of ptr. Now, thinking about that in terms of bits and integer representation, it finds the two's complement of the original value. In other words, it flips all of the bits and then adds one. This is immediately followed by the subtraction of one which effectively cancels out the addition at the end of the two's complement, making it the one's complement of the original value. i.e All the bits were flipped.
After all of that occurs, it performs a bitwise AND against ALIGN-1. Keeping in mind that ALIGN is a power of 2, this means that all the bits { b_0, b_1, ... , b_n } before b_x where b^x = ALIGN are being considered during the bitwise AND. Now since the bits being considered against the bitwise AND are the ones before b_x in ptr that are 0 ( since the bits were flipped ), we get the sum needed to be added to ptr to cause the bits before b_x to add up to ALIGN-1. Now to that sum, 1 is added. We now have the sum necessary to increase the pointer to a multiple of ALIGN. The result of the expression is stored in diff, and then diff is added to ptr. Now this alignment is guaranteed to increase ptr by at least 1 byte, so it's perfectly safe in the next line when they store the value of diff at an offset of -1 from ptr. In case you're wondering why this is done, it's so that when freeing the memory later on, and the original value returned from malloc() is needed, you can compute it by subtracting diff from the value of ptr.
Here we see that it's doing a check to make sure that the size is at least 32 bytes less than the maximum size of data allowed to be allocated. The first question one should ask when seeing this is, why 32 bytes? The answer is memory alignment. ALIGN is defined as either 16 or 32 depending on whether or not the processor supports AVX instructions. Here, it seems, they got lazy and instead of just checking to see whether it was 16 or 32, they just assumed the larger. So if the size requested was too large, it just returns NULL and that's the end of it. However, if the size wasn't too large, things become more interesting. Now the memory is allocated and aligned. If HAVE_MEMALIGN or HAVE_POSIX_MEMALIGN is defined then the corresponding function is called. However, when CONFIG_MEMALIGN_HACK is defined, some interesting code is run.
The first thing this section of code does is that it makes a call to malloc() and allocates size+ALIGN bytes of memory. From our previous observations it should be obvious that the extra bytes allocated is so that alignment can take place. Now if the allocation fails, ptr is set to NULL and the value of ptr is returned.
Now let's look at what happens if the allocation was successful. It sets diff to the result of an expression containing ptr and ALIGN that involves some bitwise operations. The first part of the expression is ((-(long)ptr - 1). Now the '-' operator at the front tells us that it's computing the negative equivalent of ptr. Now, thinking about that in terms of bits and integer representation, it finds the two's complement of the original value. In other words, it flips all of the bits and then adds one. This is immediately followed by the subtraction of one which effectively cancels out the addition at the end of the two's complement, making it the one's complement of the original value. i.e All the bits were flipped.
After all of that occurs, it performs a bitwise AND against ALIGN-1. Keeping in mind that ALIGN is a power of 2, this means that all the bits { b_0, b_1, ... , b_n } before b_x where b^x = ALIGN are being considered during the bitwise AND. Now since the bits being considered against the bitwise AND are the ones before b_x in ptr that are 0 ( since the bits were flipped ), we get the sum needed to be added to ptr to cause the bits before b_x to add up to ALIGN-1. Now to that sum, 1 is added. We now have the sum necessary to increase the pointer to a multiple of ALIGN. The result of the expression is stored in diff, and then diff is added to ptr. Now this alignment is guaranteed to increase ptr by at least 1 byte, so it's perfectly safe in the next line when they store the value of diff at an offset of -1 from ptr. In case you're wondering why this is done, it's so that when freeing the memory later on, and the original value returned from malloc() is needed, you can compute it by subtracting diff from the value of ptr.