Hacking C++ dynamic array size

∥☆過路亽.° 提交于 2019-12-25 07:00:36

问题


Pure curiosity, not to be used in production, because obviously it could cause major problems.

With C++, when you allocate new memory (var *ch = new char[x]), the size is stored essentially in ch[-1] according to the C++ specs.

The question is, is there a way to get that value? I've tried:

char* ptr = ch;
--ptr
cout << *ptr;

// AND

cout << ch[-sizeof(char)];

So is there a way to hack this? Again, pure curiosity.


回答1:


Disclaimer: Never, ever count on this working. Consider this only "toy code" and never use it in "real" software!

Often times, the new operator ends up calling right to malloc(), which is known to exhibit this behavior in many versions of libc.

The problem with your code is that your pointer is a char* but the data you're after is probably really a size_t (4 bytes on a 32-bit system).

The following code does demonstrate almost what you're after:

#include <stddef.h>        // for size_t
#include <stdio.h>

void test(size_t size) {
    size_t result;
    char* p = new char[size];

    result = *((size_t*)p - 1);
    printf("Allocated:  %d (0x%X)  Preceding value: %d (0x%X)\n",
        size, size, result, result);

    delete p;
}

int main() {
    test(1);
    test(40);
    test(100);
    test(0x100);
    test(6666);
    test(0xDEAD);
    return 0;
}

Note that I'm first casting p to a size_t*, and then subtracting 1 (which equates to sizeof(size_t) bytes).

Output:

$ ./a.exe
Allocated:  1 (0x1)  Preceding value: 19 (0x13)
Allocated:  40 (0x28)  Preceding value: 51 (0x33)
Allocated:  100 (0x64)  Preceding value: 107 (0x6B)
Allocated:  256 (0x100)  Preceding value: 267 (0x10B)
Allocated:  6666 (0x1A0A)  Preceding value: 6675 (0x1A13)
Allocated:  57005 (0xDEAD)  Preceding value: 57019 (0xDEBB)

So the output is close.


Looking at malloc/malloc.c from glibc, we see the following comment:

  Alignment:                              2 * sizeof(size_t) (default)
       (i.e., 8 byte alignment with 4byte size_t). This suffices for
       nearly all current machines and C compilers. However, you can
       define MALLOC_ALIGNMENT to be wider than this if necessary.

  Minimum overhead per allocated chunk:   4 or 8 bytes
       Each malloced chunk has a hidden word of overhead holding size
       and status information

  Minimum allocated size: 4-byte ptrs:  16 bytes    (including 4 overhead)
              8-byte ptrs:  24/32 bytes (including, 4/8 overhead)

These are excellent clues. There are two things that are probably happening:

  1. Your requested allocation sizes are being aligned up to the next alignment size.
  2. The lowest bits (not used because of the above alignment) are used for this "status information.

So we add the code to show numbers that "play along" with these rules:

#define SIZE        sizeof(size_t)
#define MAX(x,y)    ((x)>(y) ? (x) : (y))
#define align(x)    (((x)+2*SIZE-1) & ~(2*SIZE-1))
#define mask(x)     ((x) & ~0x3)

printf("align(size): 0x%X   mask(result): 0x%X\n\n",
    align(MAX(size+SIZE, 16)), mask(result));

The size also includes SIZE, and must be at least 16. This value is then aligned to the next 2*SIZE multiple. And the result we read out has the bottom 2 bits ANDed off. These are the "status information. The result:

$ ./a.exe
sizeof(size_t) = 4

size:  1 (0x1)  result: 19 (0x13)
align(size): 0x10   mask(result): 0x10

size:  40 (0x28)  result: 51 (0x33)
align(size): 0x30   mask(result): 0x30

size:  100 (0x64)  result: 107 (0x6B)
align(size): 0x68   mask(result): 0x68

size:  256 (0x100)  result: 267 (0x10B)
align(size): 0x108   mask(result): 0x108

size:  6666 (0x1A0A)  result: 6675 (0x1A13)
align(size): 0x1A10   mask(result): 0x1A10

size:  57005 (0xDEAD)  result: 57019 (0xDEBB)
align(size): 0xDEB8   mask(result): 0xDEB8

And there you have it!


Note that I'm using:

$ uname
CYGWIN_NT-6.1-WOW64

$ g++ --version
g++ (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)

Again, this is highly implementation-specific and should never be trusted. However, it is true that many allocators store the allocation size right before the actual block of memory.


See also:

  • Where Do malloc() / free() Store Allocated Sizes and Addresses?
  • glibc malloc/malloc.c - especially "malloc_chunk details" around line 1119



回答2:


No. Standard C++ provides no mechanism that given only the pointer to a dynamically allocated buffer, to determine the size of the buffer. It is assumed that internally these dynamically allocated regions are somehow tracked, such that delete [] pblah can work, but the C++ standard imposes nothing on the implementation on how it is done. Therefore what you are asking may, theoretically, be possible, it cannot be done without knowing the internals of your compiler, library, platform and implementation.




回答3:


Others already stated that it is implementation specific. On MS VS2012 I find the default allocations to be aligned at a 16 byte granularity with 16 bytes preceeding the memory block in which the number of required bytes is stored in the first sizeof(size_t) bytes of that block.

I adopted Jonathon's Test

#include <iostream>

void test(size_t const size) 
{
  char * mem = new char[size];
  std::cout << "Number of allocated bytes at " << (size_t)mem;
  std::cout << " is: " << *(size_t*)(mem-16) << std::endl;
  delete [] mem;
}

void main()
{
  test(1U);
  test(40U);
  test(256);
  test(6666);
  test(57005);
  system("pause");
}

Giving me

Number of allocated bytes at 101044204016 is: 1
Number of allocated bytes at 101044242832 is: 40
Number of allocated bytes at 101044242832 is: 256
Number of allocated bytes at 101044244064 is: 6666
Number of allocated bytes at 101044244064 is: 57005
Drücken Sie eine beliebige Taste . . .

Where

  • 101044204016 / 16 = 6315262751
  • 101044242832 / 16 = 6315265177
  • 101044244064 / 16 = 6315265254

Als see C++ allocates abnormally large amout memory for variables



来源:https://stackoverflow.com/questions/21471814/hacking-c-dynamic-array-size

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!