Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safe conversion from __int64 to size_t

I am working on Windows OS with Visual Studio 2017, and I obtained the following function to determine the size of a file from one of SO's answers:

__int64 FileSize(const char *filename)
{
    HANDLE hFile = CreateFile(filename, GENERIC_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        return -1; // error condition, could call GetLastError to find out more
    }

    LARGE_INTEGER size;
    if (!GetFileSizeEx(hFile, &size)) {
        CloseHandle(hFile);
        return -1; // error condition, could call GetLastError to find out more
    }

    CloseHandle(hFile);
    return size.QuadPart;
}

So, am using it to determine a file size, in order to allocate memory dynamically accordingly with malloc(). Since the function malloc() accept a size_t type, I assigned the return value of FileSize() function to a size_t variable, but I got the following warning:

main.cpp(67): warning C4244: 'initializing': conversion from '__int64' to '::size_t', possible loss of data

In this case, How would I safely store the size of the file within a size_t variable? I know I could cast the return value to size_t and dismiss the warning, but would it be safe/correct?

like image 647
machine_1 Avatar asked Oct 04 '17 12:10

machine_1


3 Answers

This is very much system-specific. On some systems, size_t may be smaller than int64_t, which would give the warning. But of course you can't malloc more than what will fit inside a size_t anyway.

It is most likely safe to do size_t s = (size_t)some_int64;.

However, if you are feeling paranoid, you can add a check/assert:

#include <stdint.h>
...
if(some_int64 > SIZE_MAX) // this line is fully portable
{
  halt_and_catch_fire();
}

SIZE_MAX is a constant representing the maximum value that a size_t variable can hold.

like image 62
Lundin Avatar answered Oct 03 '22 07:10

Lundin


The size_t type is implementation-defined. So there is no way to be sure the value of __int64 can be safely stored in a size_t type. I would suggest to use a static_assert:

static_assert(sizeof(__int64)<=sizeof(size_t), 
"Unable to safely store an __int64 value in a size_t variable");

This would create an error during the compile process if size_t is smaller than __int64.

See http://en.cppreference.com/w/cpp/types/size_t for more Information.

like image 36
Raynigon Avatar answered Oct 03 '22 07:10

Raynigon


size_t is smaller than __int64 when you are compiling 32-bit applications.

If you know the files you are working with are "small" (< 2 GB) you can sidestep the issue a bit by casting and just aborting if the file is somehow very large:

UINT64 size = FileSize(...);
if (size > ~(size_t)0) // You might want to use SIZE_MAX instead of the ~ trick if you want to stay portable
{
  printf("File is too large!\n");
}
else
{
  void*data = malloc((size_t) size);
  if (!data)
    printf("Out of memory, file is too large?\n");
  else
    ...
}

If on the other hand the files can be large then you cannot assume that you will be able to read the whole thing into memory at once because the machine might not have enough memory or you might run out of address space (usually around 2 GB in a 32-bit Windows process). In this case you should use memory mapped files instead with a smaller view.

like image 25
Anders Avatar answered Oct 03 '22 07:10

Anders