std::ifstream, MSVC 2012, бага?
От: nen777w  
Дата: 13.02.15 20:33
Оценка:
Нужно узнать размер файла (как еще кстати это сделать не прибегая к системному API и boost::filesystem, ну кроме банального fopen() и т.д.) ?
Вот такой код:
uint64_t file_size(const char *path)
{
    std::ifstream in(path, std::ifstream::ate|std::ifstream::binary);
    if(!in.good()) return 0;
    return in.tellg();
}


Если открывать файл размером 10 gb вернёт 0 т.е. сработает условие !in.good(), если убрать std::ifstream::ate файл откроется.
Можно посикать до конца и тогда in.tellg() — вернёт правильный размер.
Интересно почему с std::ifstream::ate на файлах >= 10Gb не работает. Бага? (Если да куда писать?)
Отредактировано 13.02.2015 20:43 nen777w . Предыдущая версия . Еще …
Отредактировано 13.02.2015 20:43 nen777w . Предыдущая версия .
Отредактировано 13.02.2015 20:35 nen777w . Предыдущая версия .
Отредактировано 13.02.2015 20:34 nen777w . Предыдущая версия .
Re: std::ifstream, MSVC 2012, бага?
От: c-smile Канада http://terrainformatica.com
Дата: 13.02.15 22:10
Оценка: +1
Здравствуйте, nen777w, Вы писали:

N>Если открывать файл размером 10 gb вернёт 0 т.е. сработает условие !in.good(), если убрать std::ifstream::ate файл откроется.

N>Можно посикать до конца и тогда in.tellg() — вернёт правильный размер.
N>Интересно почему с std::ifstream::ate на файлах >= 10Gb не работает. Бага? (Если да куда писать?)

32 bit переполнение. По всей видимости size_t используется. Попробуй в 64-bit собрать.
Re: std::ifstream, MSVC 2012, бага?
От: uzhas Ниоткуда  
Дата: 14.02.15 06:54
Оценка:
Здравствуйте, nen777w, Вы писали:

N>Нужно узнать размер файла (как еще кстати это сделать не прибегая к системному API и boost::filesystem, ну кроме банального fopen() и т.д.) ?


размер файла обычно узнают как раз через системное АПИ, через мета-данные, а не через открытие содержимого файла
в плюсах крайне скудная поддержка файлового АПИ
Re[2]: std::ifstream, MSVC 2012, бага?
От: nen777w  
Дата: 14.02.15 08:59
Оценка:
N>>Если открывать файл размером 10 gb вернёт 0 т.е. сработает условие !in.good(), если убрать std::ifstream::ate файл откроется.
N>>Можно посикать до конца и тогда in.tellg() — вернёт правильный размер.
N>>Интересно почему с std::ifstream::ate на файлах >= 10Gb не работает. Бага? (Если да куда писать?)

CS>32 bit переполнение. По всей видимости size_t используется. Попробуй в 64-bit собрать.

Я не копал вглубь, времени в нет, но приложение на котором это обнаружилось 64-битное.
Ну и не может быть (не должно по крайней мере) так чтоб для фиг знает сколько существуюшего NTFS в реализации (i/o)fstream ограничились 32 битными типами.
Потом почему если открывть а потом сикать то работает. tellg() возвращает 64-битный беззнаковый.
Бага скорее всего.
Во общем постараюсь найти время сегодня-завтра пойду в глубь посмотрю что там происходит, потом отпишусь.
Отредактировано 14.02.2015 13:14 nen777w . Предыдущая версия .
Re[2]: std::ifstream, MSVC 2012, бага?
От: nen777w  
Дата: 14.02.15 09:02
Оценка:
N>>Нужно узнать размер файла (как еще кстати это сделать не прибегая к системному API и boost::filesystem, ну кроме банального fopen() и т.д.) ?

U>размер файла обычно узнают как раз через системное АПИ, через мета-данные, а не через открытие содержимого файла

U>в плюсах крайне скудная поддержка файлового АПИ

Да я в курсе что API надёжнее всего. Просто думал может пропустл что (они ж с boost что то тащат постоянно в std) а потом вспомнил, что только недавно обсуждалось тут появление filesystem в c++14.
Re: std::ifstream, MSVC 2012, бага?
От: Sni4ok  
Дата: 15.02.15 17:16
Оценка:
Здравствуйте, nen777w, Вы писали:

N>
N>uint64_t file_size(const char *path)
N>{
N>    std::ifstream in(path, std::ifstream::ate|std::ifstream::binary);
N>    if(!in.good()) return 0;
N>    return in.tellg().seekpos();
N>}
N>
Re: std::ifstream, MSVC 2012, бага?
От: PM  
Дата: 15.02.15 18:33
Оценка:
Здравствуйте, nen777w, Вы писали:

[код поскипан]

Теоретически, использование stream.tellg() — это непереносимый способ узнать размер файла. Так как tellg() возвращает лишь некое положение в потоке данных, которое в дальнейшем может быть использовано чтобы вернуться к этому положению.

Единственный способ узнать размер потока в С++ используя IOStreams — это прочитать весь поток до конца, проигнорировав все данные и потом узнать кол-во прочитанных байт:
file.ignore( std::numeric_limits<std::streamsize>::max() );
std::streamsize length = file.gcount();
file.clear();   //  Since ignore will have set eof.
file.seekg( 0, std::ios_base::beg );


Источник: http://stackoverflow.com/a/22986486/1355844

Самый надежный вариант — использовать API целевой платформы, ну или boost::filesystem для большинства распространенных платформ.
Re[2]: std::ifstream, MSVC 2012, бага?
От: nen777w  
Дата: 16.02.15 20:17
Оценка:
N>>
N>>uint64_t file_size(const char *path)
N>>{
N>>    std::ifstream in(path, std::ifstream::ate|std::ifstream::binary);
N>>    if(!in.good()) return 0;
N>>    return in.tellg().seekpos();
N>>}
N>>


1) Зачем seekpos() ?
2) т.е. у тебя на if(!in.good()) return 0; не выходит из функции (я таки еще не посмотрел что там происходит).
Re[2]: std::ifstream, MSVC 2012, бага?
От: nen777w  
Дата: 17.02.15 11:45
Оценка:
PM>Теоретически, использование stream.tellg() — это непереносимый способ узнать размер файла. Так как tellg() возвращает лишь некое положение в потоке данных, которое в дальнейшем может быть использовано чтобы вернуться к этому положению.

PM>Единственный способ узнать размер потока в С++ используя IOStreams — это прочитать весь поток до конца, проигнорировав все данные и потом узнать кол-во прочитанных байт:

[skip]
PM>Источник: http://stackoverflow.com/a/22986486/1355844

Спасибо.

PM>Самый надежный вариант — использовать API целевой платформы, ну или boost::filesystem для большинства распространенных платформ.


Ладно. Но суть не в том что бы узнать размер файла.
Суть в этом:
std::ifstream in(path, std::ifstream::ate|std::ifstream::binary);
if(!in.good()) {
   //WTF? для файлов больше 10Gb
}

Уже давайте по чесноку что бы вообще, (хотя ИМХО оно не должно от этого зависеть) — приложение 64-битное.
Пошел копать...
Стрим открывается, потом ему делают:
if (!atendflag || fseek(fp, 0, SEEK_END) == 0)
        return (fp);    // no need to seek to end, or seek succeeded 

    fclose(fp);    // can't position at end
    return (0);


Так вот fseek() не вернул 0.

Копаем в fseek() последнее что там делают куда без дизассемблирования не докопаться это в long __cdecl _lseek_nolock()
/*ГДЕ: osHandle - какой то 64 битный дискриптор равен: 0x20 (думаю валидный)
       pos - 0
       mthd - 2
*/
if ((osHandle = (HANDLE)_get_osfhandle(fh)) == (HANDLE)-1)
        {
            errno = EBADF;
            _ASSERTE(("Invalid file descriptor",0));
            return -1;
        }

if ((newpos = SetFilePointer(osHandle, pos, NULL, mthd)) == -1)
                dosretval = GetLastError(); <<-- UUUUPS....
        else
                dosretval = 0;


GetLastError() вернул 87

Что означает:

ERROR_INVALID_PARAMETER
87 (0x57)
The parameter is incorrect.


SetFilePointer()

Идём дальше:
std::ifstream in(path, /*std::ifstream::ate|*/std::ifstream::binary);
      
if(!in.good()) {
    return 0;
}
in.seekg(0, std::ifstream::end);

//и еще раз
if(!in.good()) {
    return 0;
}

все ок!


Копаем:
Опа... а в конце то все дело теперь делает: __int64 __cdecl _lseeki64_nolock()

И уже знакомый код, только теперь он выглядит вот так и всю работу теперь делает SetFilePointerEx:
Параметры всё те же, только заиспользовали LARGE_INTEGER newpos;

if ((osHandle = (HANDLE)_get_osfhandle(fh)) == (HANDLE)-1)
        {
            errno = EBADF;
            _ASSERTE(("Invalid file descriptor. File possibly closed by a different thread",0));
            return( -1i64 );
        }

        if (!SetFilePointerEx( osHandle,
                               *(PLARGE_INTEGER)&pos,
                               &newpos,
                               mthd) )
        {
                _dosmaperr( GetLastError() );
                return( -1i64 );
        }


newpos.QuadPart будет содержать то что и ожидалось 10737418240 т.е. длинну нашего 10Gb файла.

Ну вот так вот... у меня пока всё.
Но судя по всему звать нужно было SetFilePointerEx() т.к. SetFilePointer() оперирует с LONG а вот SetFilePointerEx() LARGE_INTEGER

Бага?
Отредактировано 17.02.2015 11:48 nen777w . Предыдущая версия . Еще …
Отредактировано 17.02.2015 11:46 nen777w . Предыдущая версия .
Re[3]: std::ifstream, MSVC 2012, бага?
От: PM  
Дата: 17.02.15 14:24
Оценка:
Здравствуйте, nen777w, Вы писали:

[здесь было расследование]
N>Ну вот так вот... у меня пока всё.
N>Но судя по всему звать нужно было SetFilePointerEx() т.к. SetFilePointer() оперирует с LONG а вот SetFilePointerEx() LARGE_INTEGER

N>Бага?


Может быть. В VS2013 при открытии файла в режиме append, вызывается int fseek(file, 0, END) в недрах которого вызывается long _lseek() и в конечном итоге SetFilePointerEx(), но для больших файлов это не помогает, т.к. long у MS всегда 32 бита, даже в 64-битной программе. Поэтому внутри реализации _lseek такой код:

        /* Try to set the new file pointer */
        large_pos.QuadPart = pos;

        if (!SetFilePointerEx(osHandle, large_pos, &new_pos, mthd)) {
            _dosmaperr(GetLastError());
            return -1;
        }

        /* The call succeeded, but the new file pointer location is
           too large for the return type or a negative value.
           So, restore file pointer to saved location and return error.
         */
        if (new_pos.HighPart != 0) {
            /*  */
            SetFilePointerEx(osHandle, saved_pos, NULL, FILE_BEGIN);
            errno = EINVAL;
            return -1;
        }


Т.е. fseek() всегда возвращает ошибку при смещении больше чем на 4Gb.

Мораль? Пользуйтесь функциями платформы для получения размера файла
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.