Страницы

Поиск по вопросам

Показаны сообщения с ярлыком io. Показать все сообщения
Показаны сообщения с ярлыком io. Показать все сообщения

понедельник, 24 февраля 2020 г.

Исключение при вызове метода MoveTo у экземпляра DirectoryInfo

#c_sharp #исключения #файловая_система #io




DirectoryInfo dir = new DirectoryInfo(Directory.GetCurrentDirectory());
string s = @"C:\";
dir.MoveTo(s);


Текст в s для примера. Какой бы путь не вставил, исключение всё равно вылетает.

Исключение:


  "Процесс не может получить доступ к файлу, так как этот файл занят другим процессом."



Что не так? Вроде простейший код.



UPD 24.03.16. 17.45.

Если кому-нибудь надо, могу выложить алгоритм поиска вверх по папкам.
Ищет в текущей папке нужную папку/файл/файлы с заданным расширением. Если не найдено,
поднимается на уровень выше и ищет там, ещё выше и т. д. Опционально - поиск в подпапках,
максимальное количество уровней "подъёма" вверх, название искомой директории, маска
поиска файлов (одного файла или файлов с заданным расширением), папка начала поиска.
На выходе - путь к найденной папке. Поиск ленивый - до первой найденной папки, в
которой есть искомые файлы.
    


Ответы

Ответ 1



Из текущей папки запущенно приложение, вот и не удается переместить её. По крайней мере - это то что приходит в голову в первую очередь.

суббота, 15 февраля 2020 г.

Как записать в файл, который хранится в ресурсах?

#java #io #resources


Здравствуйте!
Подскажите пожалуйста, у меня есть текстовый файл, где хранятся очки игры, предполагал,
что файл будет хранится в ресурсах, с чтением файла я разобрался, но мне надо в него
и записывать, новый рекорд например. Как это сделать? Или подскажите как лучше поступить
в данной ситуации.
    


Ответы

Ответ 1



Путь тот же самый что и при считывании. Пример записи: public class MyClass { public static void main(String[] args) throws IllegalAccessException, IOException { String fileName = "someFile.txt"; ClassLoader classLoader = MyClass.class.getClassLoader(); Path path = Paths.get(classLoader.getResource(fileName).getPath()); writeFile(path, "test data"); } private static void writeFile(Path path, String data) throws IOException { Files.write(path, data.getBytes(), StandardOpenOption.WRITE); } } Опции записи задаются с помощью StandardOpenOption констант путем перечисления (например создать новый файл, или дописать в конец файла) Самый простой способ в Вашем случае: при старте программы считывать данные и преобразовывать в какой-нибудь класс, а затем при завершении работы перезаписывать файлик данными из этого класса.

среда, 12 февраля 2020 г.

Странности с закрытием PrintWriter в Java

#java #io


Почему такой пример выполняется, не выдвавая никаких исключений.  

PrintWriter os = new PrintWriter(System.out, true); 
os.close(); 
os.println("text");

    


Ответы

Ответ 1



PrintWriter не совсем обычный класс, если посмотреть документацию к классу, то увидим следующее: Methods in this class never throw I/O exceptions, although some of its constructors may. The client may inquire as to whether any errors have occurred by invoking {@link #checkError checkError()}. Т.е. он проглатывает все ошибки, связанные с вводом-выводом.

воскресенье, 9 февраля 2020 г.

Как именно работает метод read() у InputStream?

#java #io


Как же все-таки устроена работа метода read():


Почему метод возвращает int а не byte?
Почему возвращается byte как int в диапазоне от 0 до 255?
Почему метод не может возвращать byte?
Что вернет метод если он считает -1 из входного потока?

    


Ответы

Ответ 1



Почему метод read() класса InputStream возвращает int, а не byte? Попробуйте выполнить такой код: byte myByte = -3; // 11111101 byte[] myArray = {myByte}; InputStream in = new ByteArrayInputStream(myArray); int myInt = in.read(); System.out.println(myInt); // 253 В консоль выводится 253, а не -3. Почему так происходит? Тип данных int в Java является дополненнным до двух целым числом и использует 32 бита вместо 8. В 32х битном виде число 253 будет: 00000000000000000000000011111101 т.е. метод read() возвращает не само значение byte, а его представление в 32х битном виде. Как хранятся значения в int и в byte ? | число | 32 bit | 8 bit | ----- -------------------------------- -------- | . . | . . . . . . . . . . . . . . . . | | . . | . . . . . . . . . . . . . . . . | | -128 | 11111111111111111111111110000000 | 10000000 | | -127 | 11111111111111111111111110000001 | 10000001 | | -126 | 11111111111111111111111110000010 | 10000010 | | -125 | 11111111111111111111111110000011 | 10000011 | | . . | . . . . . . . . . . . . . . . . | . . . . | | . . | . . . . . . . . . . . . . . . . | . . . . | | -2 | 11111111111111111111111111111110 | 11111110 | | -1 | 11111111111111111111111111111111 | 11111111 | | 0 | 00000000000000000000000000000000 | 00000000 | | 1 | 00000000000000000000000000000001 | 00000001 | | 2 | 00000000000000000000000000000010 | 00000010 | | . . | . . . . . . . . . . . . . . . . | . . . . | | . . | . . . . . . . . . . . . . . . . | . . . . | | 125 | 00000000000000000000000001111101 | 01111101 | | 126 | 00000000000000000000000001111110 | 01111110 | | 127 | 00000000000000000000000001111111 | 01111111 | | . . | . . . . . . . . . . . . . . . . | | . . | . . . . . . . . . . . . . . . . | Диапазон byte в Java лежит от -128 до 127, а возвращаемое значение метода read() лежит в диапазоне от 0 до 255 Что происходит с числом byte в методе read()? Чтобы получить представление byte в int в методе read() используется побитовое «И» c числом 255, т.е. убираем лидирующие единицы. | число | 32 bit | ----- -------------------------------- | -3 | 11111111111111111111111111111101 | И | 255 | 00000000000000000000000011111111 | = | 253 | 00000000000000000000000011111101 | Чтобы из представления получить обратно значение byte в int, нужно выполнить обратную операцию побитовое «ИЛИ» c числом -256, т.е. добавляем лидирующие единицы. | число | 32 bit | ----- -------------------------------- | 253 | 00000000000000000000000011111101 | ИЛИ | -256 | 11111111111111111111111100000000 | = | -3 | 11111111111111111111111111111101 | Что происходит с числом byte == -1 в методе read()? То же самое: убираем лидирующие единицы. | число | 32 bit | ----- -------------------------------- | -1 | 11111111111111111111111111111111 | И | 255 | 00000000000000000000000011111111 | = | 255 | 00000000000000000000000011111111 |

Ответ 2



На все 4 вопроса вообще один ответ. Возвращается int потому что надо такой тип, который может вместить в себя один байт (реальные данные) плюс одно служебное значение (это тот самый -1), которое является признаком окончания чтения. Вообще конечно можно бы было спроектировать метод read() так, чтобы он возвращал byte. Но тогда этот метод в случае окончания потока либо должен был бросать исключение, либо вводить допольнительный метод, при помощи которого бы можно было проверять окончание потока. С обоими способами бы были проблемы, т.к. с исключениями у программистов всегда бы оно ловилось, а природа исключений немного другая. А с дополнительным методом нельзя заставить обязать программиста его везде вызывать (ведь если этот вызов будет опущен, то как отделить, когда из потока методом read возращается 0 с данными от 0 когда данных нет. Вообще говоря, этот метод редко используется, в основном из-за своих проблем с производительностью. Предпочтение отдаётся методу read(byte[]), который читает сразу массив байт. P.S. Другой вопрос почему тут выбрали int, а не short. Точных причин сказать не могу, но скорее потому, что short считается неполноценным братом int. Многие арифметические алгоритмы предпочитают использовать всегда int, даже когда точно известно, что диапазона short вполне хватит. И ещё есть момент с short. JVM-инструкции более заточены на int, нежели на short.

суббота, 11 января 2020 г.

Как избежать ошибки PathTooLongException?

#c_sharp #исключения #io


Имеется программа обрабатывающая различные файловые пути (напр.: C:\Users\J.Doe,
\\Server\Share, Z:\Mapped\Drive). Для однозначной обработки файлов их пути приводятся
к UNC-формату, если файл располагается не на локальном диске. Для избежания возникновения
исключения PathTooLongException можно к локальным путям добавлять префикс \\?\, но
в этом случае сам путь не обрабатывается, т.е. не раскрываются ссылки, относительные
пути и т.д.. И насколько я понимаю, это не применимо к UNC-путям (поправьте, если не прав).
Нашел статью .NET 4.6.2 and long paths on Windows 10, но как понятно из названия,
сие работает только с Windows 10 и не работает с UNC, а как быть со старыми версиями
ОС и файлами в сети?

Как принято обходить эту ошибку? Можно ли получить доступ к файлу по UNC пути, если
он длиннее MAX_PATH?
    


Ответы

Ответ 1



К сожалению, на старых версиях Windows придётся прибегать к прямому использованию WinAPI. Примеры кода есть в этой статье, придётся написать собственный вспомогательный класс вместо File. Я пользуюсь вот таким классом: public static class LongPathFile { [Flags] public enum EFileAccess : uint { GenericRead = 0x80000000, GenericWrite = 0x40000000, GenericExecute = 0x20000000, GenericAll = 0x10000000, } [Flags] public enum EFileShare : uint { None = 0x00000000, Read = 0x00000001, Write = 0x00000002, Delete = 0x00000004, } public enum ECreationDisposition : uint { New = 1, CreateAlways = 2, OpenExisting = 3, OpenAlways = 4, TruncateExisting = 5 } [Flags] public enum EFileAttributes : uint { Readonly = 0x00000001, Hidden = 0x00000002, System = 0x00000004, Directory = 0x00000010, Archive = 0x00000020, Device = 0x00000040, Normal = 0x00000080, Temporary = 0x00000100, SparseFile = 0x00000200, ReparsePoint = 0x00000400, Compressed = 0x00000800, Offline = 0x00001000, NotContentIndexed = 0x00002000, Encrypted = 0x00004000, Write_Through = 0x80000000, Overlapped = 0x40000000, NoBuffering = 0x20000000, RandomAccess = 0x10000000, SequentialScan = 0x08000000, DeleteOnClose = 0x04000000, BackupSemantics = 0x02000000, PosixSemantics = 0x01000000, OpenReparsePoint = 0x00200000, OpenNoRecall = 0x00100000, FirstPipeInstance = 0x00080000 } [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern SafeFileHandle CreateFile( string lpFileName, EFileAccess dwDesiredAccess, EFileShare dwShareMode, IntPtr lpSecurityAttributes, ECreationDisposition dwCreationDisposition, EFileAttributes dwFlagsAndAttributes, IntPtr hTemplateFile); public static SafeFileHandle GetCreateHandle(string path) { var handle = CreateFile( path, EFileAccess.GenericWrite, EFileShare.None, IntPtr.Zero, ECreationDisposition.CreateAlways, 0, IntPtr.Zero); var lastWin32Error = Marshal.GetLastWin32Error(); if (handle.IsInvalid) throw new Win32Exception(lastWin32Error); return handle; } public static FileStream Create(string path) { return new FileStream(GetCreateHandle(path), FileAccess.Write); } public static SafeFileHandle GetReadHandle(string path) { var handle = CreateFile( path, EFileAccess.GenericRead, EFileShare.None, IntPtr.Zero, ECreationDisposition.OpenExisting, 0, IntPtr.Zero); var lastWin32Error = Marshal.GetLastWin32Error(); if (handle.IsInvalid) throw new Win32Exception(lastWin32Error); return handle; } public static FileStream OpenRead(string path) { return new FileStream(GetReadHandle(path), FileAccess.Read); } } Использовать так же, как и File.Create/File.OpenRead: using (var file = LongPathFile.Create(path)) stream.CopyTo(file);

четверг, 9 января 2020 г.

Ошибка I\O error 103 (Delphi XE3)

#delphi #io


Почему выскакивает ошибка при обработке файла: I\O error 103 ?

var
  fall, f1: TextFile;
  S: string;
begin
    AssignFile(fall, OpenDialog1.FileName);
    reset(fall);

    if CheckBox1.Checked then
    begin
        AssignFile(f1, 'Organization.txt');
        rewrite(f1);
    end;

    //работаем по строкам в файле
    while not Eof(fall) do
    begin
        //читаем строку
        ReadLn(fall, S);
        if CheckBox1.Checked and (Pos(AnsiLowerCase(Edit1.Text), AnsiLowerCase(s))
> 0) then
            writeln(f1, S);
    end;

    CloseFile(fall);
    CloseFile(f1); // <<-- Ошибка выскакивает тут
end;

    


Ответы

Ответ 1



if CheckBox1.Checked then CloseFile(f1);

Ответ 2



Ошибка, с большой долей вероятности, случается потому, что файлы вы открываете на чтение/запись по условию, а вот закрываете файл затем уже в любом случае. Скорее всего, одно из условий: if CheckBox1.Checked then if CheckBox2.Checked then if CheckBox3.Checked then у вас не выполняется, но при этом CloseFile вы делаете для всех файлов без условий. Исправьте это, введя те же самые условия и для CloseFile.

среда, 25 декабря 2019 г.

Сочетания клавиш в консоли, ввод-вывод

#cpp #windows #input #io #hotkeys


Учу С++. Хочу разобраться, как работает ввод-вывод в консоли.
Уже понял, что если вводишь в cin слова через пробел/табуляцию, он считывает их через
этот данный разделитель и выводит в cout. К примеру, ввожу "hello man", - выводит "helloman".
Это нормально, все ясно.
Также я узнал, что CTRL+Z останавливает процесс и означает символ конца файлы при
считывании. Нажимаю ctrl+Z (перед этим не ввожу символы) и процесс завершается.

Вопрос 1: почему, если я ввожу, к примеру "hello ^Z" (^Z я именно ввел сочетанием
клавиш, а не вручную сначала ^, потом Z), то сначала в потоке вывода появляется hello,
а за ним ничего не появляется, только если нажму enter, выводится вопросительный знак
в квадратике! (что это вообще такое?)

Вопрос 2: Если потом cin считывает символы через разделители, почему тогда он не
считал сначала hello, потом ^Z и в соответствии с командой не завершил процесс, считав
символ конца файла, как его называют в книге Страуструп.
Объясните, пожалуйста.

int main() {


    string current;

    while(cin>>current){

        cout << current;    
    }
}

    


Ответы

Ответ 1



Это особенности работы cmd.exe. cmd.exe отправляет текст в поток ввода построчно, и Ctrl+Z закрывает поток ввода только если до него не были введены другие символы. В этом можно убедиться переписав программу следующим образом: #include #include int main() { std::string s; while (std::cin >> s) for (int b : s) std::cout << std::hex << b << ' '; } Если что-то было введено до ^Z, то он интерпретируется как \x1a (символ замены), и всё что введено в этой строке далее - игнорируется: > test.exe 1^Z23 4 31 1a 34 При этом по умолчанию std::istream не считает \1a разделителем слов. Если до ^Z ничего не вводить, то поток закрывается, а последующие символы в строке игнорируются: > test.exe 123 31 32 33 ^Z456 >

воскресенье, 15 декабря 2019 г.

Си: проблема с getchar() и EOF(^Z) в Windows консоли

#c #windows #console #io


Уже очень долгое время пытаюсь понять: 

Почему цикл не завершается, если я введу "dfkjsdf^Z", в то время как при
"dfkjsdf (тут я нажимаю Enter) ^Z" - завершается? То есть, как сделать так, чтобы
он вышел из цикла, если я нажимаю CTRL+Z до того, как нажму Enter

Это, пожалуй, самое непонятное для меня в языке Си. И сколько бы я не рылся в гугле,
ответа все же найти не смог..

int main() {
    int c;
    int i = 0;
    int arr[10];
    while((arr[i] = getchar()) != EOF && i < 10) {
        printf("arr[i] is %c\n",arr[i]);
        i++;
    }
    return 0;
}

    


Ответы

Ответ 1



Это не имеет никакого отношения к языку С, а зависит только от алгорима обработки комбинации Ctrl-Z консолью Windows и интерпретацией результатов этой обработки той реализацией стандартной библиотеки, которую вы используете. Ввод в Windows терминале буферизуется построчно. При этом обработка присутствующих в буфере символов ^Z следует довольно запутанному алгоритму (по крайней мере при использовании стантартной библиотеки из комплекта MSVC). Комбинация Ctrl-Z сама по себе не вызывает "проталкивания" накопленного буфера на выход (в отличие от комбинации Ctrl-D в Linux). Она лишь добавляет во входной буфер символ ^Z, т.е. \x1a. Вы можете нажать Ctrl-Z несколько раз, помещая во входной буфер несколько символов ^Z. После этого вы можете продолжать вводить что-то еще. Чтобы все-таки послать накопленный буфер ожидающему процессу, придется нажать Enter. Если входной буфер содержит какие-то символы до первого появления символа ^Z, то ожидающий ввода процесс увидит все эти символы, после чего процесс увидит один символ ^Z, т.е. \x1a. Это будет просто символ \x1a. Никакой ситуации "конец файла" при этом не возникнет. Однако остаток входного буфера (после первого символа ^Z) процессу виден не будет, как будто его и не было. То есть если вы введете в Windows терминале последовательность abc^Z^Zdef^Zghi и нажмете Enter, то на вход ваш процесс получит символы a, b, c и \x1a. Весь остальной ввод пропадет бесследно. Заметьте, что при этом "пропадает" и символ перевода строки, cгенерированный нажатием Enter. Если входной буфер сразу же начинается с символа ^Z, то входной буфер считается пустым. Все его содержимое пропадает, не происходит даже чтения символа ^Z. Возникает ситуация "конец файла". То есть если вы введете в Windows терминале последовательность ^Zdef и нажмете Enter, то на вход ваш процесс не получит вообще ничего. Вместо этого функция ввода сообщит вам, что наткнулась на конец файла. Поэтому для того, чтобы создать в буферизованном консольном вводе ситуацию "конец файла" придется вводить ^Z в самом начале новой строки. Если вам нужна посимвольная обработка входа, то можно предварительно отключить построчную буферизацию ввода HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); DWORD dwMode; GetConsoleMode(hIn, &dwMode); dwMode &= ~ENABLE_LINE_INPUT; SetConsoleMode(hIn, dwMode); В таком варианте каждый введенный символ будет немедленно читаться вашей getchar() и символ ^Z будет немедленно интерпретироваться как конец файла.

среда, 11 декабря 2019 г.

Java. Вопрос по архитектуре сервера для игры

#java #сокет #tcp_ip #io


Думаю над архитектурой сервера для игры и встал вопрос над тем, как обрабатывать
подключения клиентов. Протокол - tcp/ip.

Стоит выбор между многопоточной архитектурой (1 клиент - 1 поток) и асинхронным вводом/выводом
(когда чтение не блокирует поток, если данных нет, а возвращает 0 и поток может обрабатывать
других клиентов, то есть 1 поток - много клиентов).

Игра - mmorpg, где потенциально люди могут стоять в городе и не передавать почти
никакого ввода на сервер, но так же бывают динамичные моменты где важен быстрый ответ
сервера. 



Потоки при остановке не используют CPU циклы, более отзывчивые, однако держат оперативную
память, чего бы не хотелось в слабоактивных местах, где от игроков почти не идет ввода. 



Асинхронный подход использует меньше памяти, менее уязвим к слабоактивным клиентам,
но менее эффективен для активных клиентов.

Выбор очень сложный, поэтому хотел бы спросить совета у более опытных, которые имели
опыт с данными подходами, спасибо.
    


Ответы

Ответ 1



Я бы предложил динамический пулл потоков. Плюсы от него ты знаешь. А размер этого пулла можно изменять в зависимости от нагрузки, чем и сэкономишь на CPU, и достигнешь достаточного быстродействия в зависимости от нужды пользователей

суббота, 7 декабря 2019 г.

Работа с stdin и stdout дочернего процесса одновременно в lua

#linux #lua #процесс #io


При написании lua скрипта наткнулся на одну вещь: io.popen, судя по документации
не может передать одновременно дескрипторы stdin и stdout запускаемого процесса.

Пример проблемного кода:

cmdexecute = function(cmd, input)
    local f = io.popen(cmd, "w");
    if tostring(input) ~= nil then
        f:write(tostring(input));
    end;
    local l = f:read("*a");
    f:close();
    return (l);
end;

if cmdexecute('xargs ps', 'aux') ~= nil then
    print 'ps aux что-то выдал';
else
    print 'ps aux промолчал';
end;


Результат выполнения:

Длинный-длинный вывод ps aux...
ps aux промолчал


А должно быть по идее:

ps aux что-то выдал


Вопрос: как получить дескрипторы и stdin, и stdout из popen? Или, возможно, есть
какое-то другое решение задачи, без io.popen?

Upd: по причине актуальности вопроса поясняю: решения с ffi допустимы.
    


Ответы

Ответ 1



Вам должен помочь модуль lua-subprocess Функция subprocess.popen принимает последним аргументом таблицу с именованными опциями. Среди них есть опции stdin, stdout и stderr, куда можно передать строку (имя файла), число (системный дескриптор файла), файловый объект или специальную константу subprocess.PIPE, которая создаст пайп для этого потока и сохранит его в возвращаемом объекте.

Ответ 2



io.popen дает интерфейс к системной posix функции popen, да, она может работать только в одну сторону. В вашем случае писать что то в ps не нужно. Если открыть ее для чтения - она конечно прочитает. Но я так понимаю ps у вас только для примера ... В POSIX для работы с обоими файловыми дескрипторами вызываемого процесса есть только 1 путь. Создание пар сокетов, привязка одного из концов этих соединений к файловым дескрипторам 0 и 1 (dup2). После чего выполняется fork и дочерний процесс вызывает нужную программу. В Lua судя по документации подобных средств не предусмотрено. Так что прямой ответ на ваш вопрос - получить и stdin и stdout одновременно невозможно. Из решений вижу только перенаправление вывода программы в файл и последующее чтение этого файла. Или ввод для программы пишется заранее в файл и подается на вход. И можно еще поэкспериментировать с именованными каналами. mkfifo создать именованный канал в файловой системе направить его в программу или запись из программы перенаправить в него и в lua открыть этот канал как обычный файл и попробовать писать/читать.

понедельник, 25 ноября 2019 г.

printf как средство печати переменных в С


Я не знаю как точно сформулировать то, что я хочу спросить, но выглядит это следующим образом:


Как напечатать содержимое переменной в С:

char msg = 'k';
printf("%c", msg);

Как напечать содержимое переменной в С++:

char msg = 'k';
cout << msg;



А теперь вопросы:


В C++ для вывода не нужно указывать модификатор, тогда почему в С нет аналога cout (я все понимаю, что язык гораздо старше C++, но все же)?
Мне часто пишут, что тип в printf нужно приводить:

char msg = 'k';
printf("%f", (float)msg);



А зачем? В аналоге С++, этого же не надо делать!
Почему я не могу написать так: printf("%f", msg);? В чем причина? Разве printf не приведет данные самостоятельно к тому модификатору, который мы указываем?
    


Ответы

Ответ 1



В C, printf(format, msg) в исходном коде вызывает одну и ту же функцию вне зависимост от типа, значения msg переменной. C — статически типизированный язык, это значит, чт во время исполнения printf функция не знает, что msg это char, более того printf даж не знает сколько её аргументов передали. Поэтому приходится руками в format строке задавать желаемое представление: значение какого типа из памяти, в которой msg лежит, загрузить и как это значение отформатировать, чтобы получившиеся байты затем в stdout записать. В C++, cout << msg для разных типов msg может скомпилировать вызов разных функци (перегрузки оператора <<). Вы легко можете определить свой оператор <<, к примеру, чтобы выводить векторы для отладки: #include #include template std::ostream& operator << (std::ostream& os, const std::vector& v) { for (auto&& x : v) os << x << ' '; return os; } int main() { std::vector v {1, 2, 3}; std::cout << v << '\n'; } Пример: $ g++ -std=c++11 main.cc -o main $ ./main 1 2 3 Упрощая, можно представить, что компилятор для cout << msg генерирует вызов print_char(msg) если msg это char, print_float(msg), если msg это float, print_vector(msg), если ms это vector итд. Здесь каждая функция знает какой тип она принимает и для каждого типа используется одно представление в виде байт по умолчанию, если iomanip не используются). К примеру, для int выводятся его десятичные цифры (а не hex или что-то другое) по умолчанию. В C11 появился _Generic, который позволяет разные функции в зависимости от тип аргумента (controlling-expression) вызывать, поэтому можно определить print_arg(arg), которое бы работало для разных типов arg (один аргумент). Почему я не могу написать так: printf("%f", msg);? В чем причина? Разве print не приведет данные самостоятельно к тому модификатору, который мы указываем? ... printf("%f", (float)msg); \\ 107.000000, а printf("%f", msg); \\ 0.000000 почему ответ разный? При вызове вариативных (variadic) функций таких как printf, объявленных с многоточие (...) в параметрах, которые могут принимать разное количество аргументов c неизвестным компилятору ожидаемыми типами (С компилятор не обязан понимать язык printf format-строк и значение format может быть вообще неизвестно во время компиляции), происходят преобразования по умолчанию: char неявно в int превращается (integer promotion) или в unsigned int, если значение char не представимо в int на данной платформе (экзотика). Аналогично, float в double превращается при вызове printf() — поэтому не требуется %lf для double в printf(), а используется просто %f. Для случая, c printf можно использовать простую модель: компилятор кладёт аргумент (int, double, etc) в область памяти, а printf читает их оттуда в соответствии с инструкциям в format строке — printf выступает в роли мини-компьютера: format задаёт программу и printf читает память (va_arg), в которой её аргументы лежат, форматирует их и записывает получившиеся байты в stdout. (double)msg объект может отличаться в памяти от (int)msg объекта. Поэтому print может видеть разные битовые паттерны в памяти и соответственно, результат printf("%f", (int)msg) и printf("%f", (double)msg) могут быть разными (одна и та же инструкция: %f применяется к разному содержимому в памяти). Пример для моей машины (как типы выглядят в памяти может зависеть от платформы (операционно системы + процессор) и опций компилятора). Байты в памяти я буду в виде hexdump показывать (к примеру: 6B16 == 10710). Для printf("%c", msg): 'k' в C имеет тип int — 6B 00 00 00 char msg — 6B в printf это передаётся как int — 6B 00 00 00 %c берёт этот аргумент 6B 00 00 00 и превращает его в unsigned char (6B) и соответствующий байт (6B) выводится в stdout. См. printf документацию для c формата. Для printf("%f", (float)msg): msg (6B) преобразуется в (float)msg — 00 00 D6 42 что передаётся в printf в виде double — 00 00 00 00 00 C0 5A 40 %f интерпретирует память 00 00 00 00 00 C0 5A 40 как число с плавающей точкой и выводи в фиксированном формате (6 знаков после запятой): 107.000000 (символ для точки от локали может зависеть). Соответствующие байты в ascii кодировке, которые в stdout пишутся: 31 30 37 2e 30 30 30 30 30 30 Для printf("%f", msg): msg (char — 6B) передаётся в printf в виде int — 6B 00 00 00 %f интерпретирует память 6B 00 00 00 XX XX XX XX как число с плавающей точкой 5.3e-322 (если XX == 00) и выводится 0.000000 Так как при неверном формате поведение не определено (undefined behavior — UB), т printf("%f", msg) может сделать всё что угодно, хоть ракеты запустить. На моей машине результат printf("%f", msg) зависит от предыдущего кода к примеру: char c = 'k'; printf("%f\n", (float)c); printf("%f\n", c); печатает: 107.000000 107.000000 # XX XX XX XX == 00 C0 5A 40 (остатки от предыдущего вызова) но: printf("%f\n", 5.3e-322); printf("%f\n", c); печатает: 0.000000 0.000000 # XX XX XX XX == 00 00 00 00 (остатки от предыдущего вызова) Убедитесь, что для форматов известных во время компиляции (к примеру "%f\n") ва компилятор генерирует предупреждения для неверных типов — UB следует избегать. printf("%i", (int)msg); \\\ 107 и printf("%i", msg); в обоих случая msg char передаётся как int (6B 00 00 00) и при одинаковых форматах, результат должен быть одинаковым (6B16 == 10710). Можно посмотреть содержимое переменных на вашей машине с помощью С кода: #include static void print_memory(unsigned char *memory, size_t n) { for (unsigned char *p = memory; p != memory + n; ++p) printf("%02X ", *p); puts(""); } int main(void) { char c = 'k'; print_memory((unsigned char *)&c, sizeof c); int i = c; print_memory((unsigned char *)&i, sizeof i); float f = c; print_memory((unsigned char *)&f, sizeof f); double d = f; print_memory((unsigned char *)&d, sizeof d); } Пример: $ gcc -std=c99 main.c -o main $ ./main 6B 6B 00 00 00 00 00 D6 42 00 00 00 00 00 C0 5A 40 Как double в памяти может выглядеть Пример из ответа, применённый к 107.0: число двойной точности в IEEE 754 формат представляется как d = ±знак · (1 + мантисса / 252) · 2порядок − 1023 знак, мантисса и порядок упакованы в двоичном представлении как: 00 00 00 00 00 C0 5A 40 // little-endian (8 byte as hex) 40 5A C0 00 00 00 00 00 // big-endian 0100000001011010110000000000000000000000000000000000000000000000 # 64-bit ^ |-самый левый бит знак= 0 (положительный) ^ ^ |---------| Затем 11 бит порядок= 0b10000000101 (==1029) ^ ^ |--------------------------------------------------| Оставшиеся 52 бита манитисса= 0b1010110000000000000000000000000000000000000000000000 = 3025855999639552 = 0xac00000000000 Всё вместе: d = +(1 + 3025855999639552 / 252) * 2(1029 - 1023) = 64 + 3025855999639552 / 70368744177664 = (4503599627370496 + 3025855999639552) / 70368744177664 = 7529455627010048 / 70368744177664 = 107.0 Это демонстрирует, почему 107.0 может быть представлено в памяти как 00 00 00 0 00 C0 5A 40. Пример урезанной printf функции #include // va_list, va_arg() // emulate some printf() functionality using writec() static void print(const char* format, ...) { va_list args; va_start(args, format); int infmt = 0, is_char = 0; union { int i; double f; } arg; char buffer[23] = {0}; // enough for %c, %d, %f for (const char *p = format; *p; ++p) { if (infmt) { // print arg infmt = 0; switch(*p) { case '%': // "%%": print % literally writec(*p); break; case 'c': // "%c": print char is_char = 1; // fall through case 'd': // "%d": print int case 'i': // "%i": print int arg.i = va_arg(args, int); // load int arg if (is_char) { is_char = 0; writec((unsigned char)arg.i); // format as char, write } else { itoa(arg.i, buffer, sizeof buffer); // format as int for (char *pb = buffer; *pb; ++pb) writec(*pb); // write } break; case 'f': arg.f = va_arg(args, double); // load double arg ftoa(arg.f, buffer, sizeof buffer); // format as floating point for (char *pb = buffer; *pb; ++pb) writec(*pb); // write break; default: arg.i = va_arg(args, int); // load int arg scpy(buffer, ""); for (char *pb = buffer; *pb; ++pb) writec(*pb); // write }; } else if (*p != '%') { // print literally writec(*p); } else { // *p == '%' infmt = 1; } } va_end(args); } switch используется, чтобы распознать описатели преобразований (%d) в format-строке va_arg() загружает аргументы нужного типа вспомогательные функции itoa() и ftoa() форматируют int и double соответственно writec() пишет один байт в stdout. Это определение print() достаточно для кода: int main(void) { char msg = 'k'; print("%c %i %i %f %x\n", msg, msg, (int)msg, (float)msg, msg); print("%c ", msg); print("%i ", msg); print("%i ", (int)msg); print("%f\n", (float)msg); print("%f\n", msg); // XXX UB } Пример: $ cc -std=c99 print-example.c -o print-example $ ./print-example k 107 107 107.000000 k 107 107 107.000000 107.000000 Чтобы скомпилировать, достаточно вспомогательные функции определить (определени перед print() нужно вставить): #include // POSIX write() static void writec(unsigned char c) { write(1, &c, 1); } static void scpy(char* dest, const char* src) { while (*dest++ = *src++); } static void ftoa(double d, char* buffer, int n) { if (d == 107) //XXX scpy(buffer, "107.000000"); else scpy(buffer, "XXX"); } /// format positive int as decimal ascii digits static char* utoa_rec(unsigned i, char* buffer, int *pn) { if (i >= 10) buffer = utoa_rec(i / 10, buffer, pn); if ((*pn)-- > 0) *buffer++ = '0' + (i % 10); return buffer; } static void itoa(int i, char* buffer, int n) { if (i < 0) { i = -i; //XXX ignore INT_MIN if (n-- > 0) *buffer++ = '-'; // sign } buffer = utoa_rec(i, buffer, &n); if (n > 0) *buffer++ = '\0'; } Определения функций приведены, чтобы можно было пример запустить, но фактически он просто заглушками являются (не для повторного использования), чтобы только продемонстрировать одну из простейших print(format, ...) реализаций. Вот пример полной реализации vfprintf() из glibc.

Ответ 2



почему в С нет аналога cout Функциональность cout в языке С++ критически завязана на механизм перегрузки функци на уровне библиотеки, т.е. фактически на существовании доступного библиотеке (и пользователю механизма перегрузки функций, а также сопутствующего механизма перегрузки операторов. Правильная версия функции вывода выбирается механизмом перегрузки на основе анализа типа указанного вами аргумента. В языке С нет механизмов перегрузки функций и перегрузки операторов на уровне пользователя или библиотеки. Поэтому нет и таких внешне "типонезависимых" операций ввода-вывода. В версии С11 стандарта языка С появился механизм generic-выражений, который може использоваться для эмуляции пользовательской/библиотечной перегрузки функций. Этот механизм, например, используется (может использоваться) в стандартном заголовочном файле для реализации "перегруженных" математических функций-макросов. Но реализаций "типонезависимых" функций ввода-вывода в стандартной библиотеке н делалось. Если есть желание - можете воспользоваться этим новым механизмом и попробовать реализовать их самостоятельно. Мне часто пишут что тип в printf нужно приводить. А зачем? Разве printf не приведет данные самостоятельно к тому модификатору, который мы указываем? printf - это так называемая variadic функция. Все аргументы этой функции, кроме первого, являются variadic аргументами. Они соответствуют ... в объявлении списка параметров функции int printf( const char* format, ... ); Такие аргументы передаются через особый механизм предачи параметров. Его особенность является то, что сама функция printf абсолютно ничего не знает о типах фактически передаваемых аргументов и по этой причине ничего самостоятельно привести к правильному типу не может. С чисто практической точки зрения вы можете считать, что все variadic аргументы записываютс в непрерывный бинарный поток. Внутри себя функция printf будет читать бинарные данны из этого потока при помощи механизма va_list/va_start/va_arg. Что именно записано этот поток функция printf сама по себе знать не может. Поэтому разбирать этот бинарны поток на части она будет в соответствии с тем форматом, который вы сами ей снаружи передали. И если вы ей "наврете" в этом формате, то она сама ничего не подозревая, будет разбирать этот бинарный поток неправильно. По этой причине все данные, которые вы кладете в этот бинарный поток, должны в точности соответствовать тем спецификаторам формата, которые вы указали в форматной строке. Реализации не обязаны реализовать передачу variadic аргументов именно таким образом, но особенности передачи variadic аргументов эта модель иллюстрирует достаточно точно.

Ответ 3



Для работы cout необходима перегрузка оператора <<, причём для каждого типа она своя, потому и не требуется модификаторов. В Си операторы не перегружаются. Мне кажется, слово "приведёт" не совсем точно, я бы сказал "интерпретирует": print выведет значение переменной так, как если бы она была того типа, что указан в строк формата. Переменная при этом не меняет значения. Сама printf не имеет возможности определить соответствие аргументов строке формата на этапе выполнения (а компилятор Си не учит программиста, как жить и где разложить себе грабли). Поэтому возможны всякие сюрпризы, если случайно допустить ошибку в строке формата и спутать типы или количество аргументов.

среда, 12 июня 2019 г.

Как работает Files.move() и быстрое перемещение файлов

Столкнулся с задачей, что нужно переместить файл из директории пользователя во временную директорию программы. Для этой задачи было найдено два метода:
File.renameTo() — стандартный вариант перемещения файлов. Files.move() — более новый вариант перемещения файлов.
В документации у первого метода было слишком много "но" и он мог завершиться без результата, но и без ошибки. Поэтому было решено перейти ко второму варианту. На сколько я понял, если файл находится на том же разделе, что и новое место положение, происходит переименование и это занимает секунду.

Вопроса, фактически, два:
Какой самый устойчивый способ переместить файл на другой раздел и безопасно ли это делать методом move? Как этот способ работает "под капотом" (будет ли какое-то специфическое переименование или файл просто скопируется)?


Ответ

Если посмотреть на реализацию метода move, то мы увидим следующий код
public static Path move(Path source, Path target, CopyOption... options) throws IOException { FileSystemProvider provider = provider(source); if (provider(target) == provider) { // same provider provider.move(source, target, options); } else { // different providers CopyMoveHelper.moveToForeignTarget(source, target, options); } return target; }
Как видно из кода есть два варианта:
Если перенос осуществляется в пределах одного раздела, то происходит перенос файла Если перенос осуществляется на другой раздел, то происходит копирование файла с последующим удалением исходного
/** * Simple move implements as copy+delete for use when source and target are * associated with different providers */ static void moveToForeignTarget(Path source, Path target, CopyOption... options) throws IOException { copyToForeignTarget(source, target, convertMoveToCopyOptions(options)); Files.delete(source); }
Для обеспечения надежности вы можете использовать ключ ATOMIC_MOVE - он гарантирует, что операция перемещения будет произведена атомарно, то есть операция либо выполняется целиком, либо не выполняется вовсе.

понедельник, 6 мая 2019 г.

Исключение при вызове метода MoveTo у экземпляра DirectoryInfo


DirectoryInfo dir = new DirectoryInfo(Directory.GetCurrentDirectory()); string s = @"C:\"; dir.MoveTo(s);
Текст в s для примера. Какой бы путь не вставил, исключение всё равно вылетает.
Исключение:
"Процесс не может получить доступ к файлу, так как этот файл занят другим процессом."
Что не так? Вроде простейший код.

UPD 24.03.16. 17.45.
Если кому-нибудь надо, могу выложить алгоритм поиска вверх по папкам. Ищет в текущей папке нужную папку/файл/файлы с заданным расширением. Если не найдено, поднимается на уровень выше и ищет там, ещё выше и т. д. Опционально - поиск в подпапках, максимальное количество уровней "подъёма" вверх, название искомой директории, маска поиска файлов (одного файла или файлов с заданным расширением), папка начала поиска. На выходе - путь к найденной папке. Поиск ленивый - до первой найденной папки, в которой есть искомые файлы.


Ответ

Из текущей папки запущенно приложение, вот и не удается переместить её.
По крайней мере - это то что приходит в голову в первую очередь.

четверг, 25 апреля 2019 г.

Как записать в файл, который хранится в ресурсах?

Здравствуйте! Подскажите пожалуйста, у меня есть текстовый файл, где хранятся очки игры, предполагал, что файл будет хранится в ресурсах, с чтением файла я разобрался, но мне надо в него и записывать, новый рекорд например. Как это сделать? Или подскажите как лучше поступить в данной ситуации.


Ответ

Путь тот же самый что и при считывании. Пример записи:
public class MyClass { public static void main(String[] args) throws IllegalAccessException, IOException { String fileName = "someFile.txt"; ClassLoader classLoader = MyClass.class.getClassLoader(); Path path = Paths.get(classLoader.getResource(fileName).getPath()); writeFile(path, "test data"); }
private static void writeFile(Path path, String data) throws IOException { Files.write(path, data.getBytes(), StandardOpenOption.WRITE); } }
Опции записи задаются с помощью StandardOpenOption констант путем перечисления (например создать новый файл, или дописать в конец файла) Самый простой способ в Вашем случае: при старте программы считывать данные и преобразовывать в какой-нибудь класс, а затем при завершении работы перезаписывать файлик данными из этого класса.

понедельник, 22 апреля 2019 г.

Странности с закрытием PrintWriter в Java

Почему такой пример выполняется, не выдвавая никаких исключений.
PrintWriter os = new PrintWriter(System.out, true); os.close(); os.println("text");


Ответ

PrintWriter не совсем обычный класс, если посмотреть документацию к классу, то увидим следующее:
Methods in this class never throw I/O exceptions, although some of its constructors may. The client may inquire as to whether any errors have occurred by invoking {@link #checkError checkError()}.
Т.е. он проглатывает все ошибки, связанные с вводом-выводом.

среда, 5 декабря 2018 г.

Java. Вопрос по архитектуре сервера для игры

Думаю над архитектурой сервера для игры и встал вопрос над тем, как обрабатывать подключения клиентов. Протокол - tcp/ip
Стоит выбор между многопоточной архитектурой (1 клиент - 1 поток) и асинхронным вводом/выводом (когда чтение не блокирует поток, если данных нет, а возвращает 0 и поток может обрабатывать других клиентов, то есть 1 поток - много клиентов).
Игра - mmorpg, где потенциально люди могут стоять в городе и не передавать почти никакого ввода на сервер, но так же бывают динамичные моменты где важен быстрый ответ сервера.

Потоки при остановке не используют CPU циклы, более отзывчивые, однако держат оперативную память, чего бы не хотелось в слабоактивных местах, где от игроков почти не идет ввода.

Асинхронный подход использует меньше памяти, менее уязвим к слабоактивным клиентам, но менее эффективен для активных клиентов.
Выбор очень сложный, поэтому хотел бы спросить совета у более опытных, которые имели опыт с данными подходами, спасибо.


Ответ

Я бы предложил динамический пулл потоков. Плюсы от него ты знаешь. А размер этого пулла можно изменять в зависимости от нагрузки, чем и сэкономишь на CPU, и достигнешь достаточного быстродействия в зависимости от нужды пользователей

четверг, 29 ноября 2018 г.

Сочетания клавиш в консоли, ввод-вывод

Учу С++. Хочу разобраться, как работает ввод-вывод в консоли. Уже понял, что если вводишь в cin слова через пробел/табуляцию, он считывает их через этот данный разделитель и выводит в cout. К примеру, ввожу "hello man", - выводит "helloman". Это нормально, все ясно. Также я узнал, что CTRL+Z останавливает процесс и означает символ конца файлы при считывании. Нажимаю ctrl+Z (перед этим не ввожу символы) и процесс завершается.
Вопрос 1: почему, если я ввожу, к примеру "hello ^Z" (^Z я именно ввел сочетанием клавиш, а не вручную сначала ^, потом Z), то сначала в потоке вывода появляется hello, а за ним ничего не появляется, только если нажму enter, выводится вопросительный знак в квадратике! (что это вообще такое?)
Вопрос 2: Если потом cin считывает символы через разделители, почему тогда он не считал сначала hello, потом ^Z и в соответствии с командой не завершил процесс, считав символ конца файла, как его называют в книге Страуструп. Объясните, пожалуйста.
int main() {
string current;
while(cin>>current){
cout << current; } }


Ответ

Это особенности работы cmd.exe
cmd.exe отправляет текст в поток ввода построчно, и Ctrl+Z закрывает поток ввода только если до него не были введены другие символы. В этом можно убедиться переписав программу следующим образом:
#include #include int main() { std::string s; while (std::cin >> s) for (int b : s) std::cout << std::hex << b << ' '; }
Если что-то было введено до ^Z, то он интерпретируется как \x1a (символ замены), и всё что введено в этой строке далее - игнорируется:
> test.exe 1^Z23 4 31 1a 34
При этом по умолчанию std::istream не считает \1a разделителем слов.
Если до ^Z ничего не вводить, то поток закрывается, а последующие символы в строке игнорируются:
> test.exe 123 31 32 33 ^Z456
>

пятница, 26 октября 2018 г.

Си: проблема с getchar() и EOF(^Z) в Windows консоли

Уже очень долгое время пытаюсь понять:
Почему цикл не завершается, если я введу "dfkjsdf^Z", в то время как при "dfkjsdf (тут я нажимаю Enter) ^Z" - завершается? То есть, как сделать так, чтобы он вышел из цикла, если я нажимаю CTRL+Z до того, как нажму Enter
Это, пожалуй, самое непонятное для меня в языке Си. И сколько бы я не рылся в гугле, ответа все же найти не смог..
int main() { int c; int i = 0; int arr[10]; while((arr[i] = getchar()) != EOF && i < 10) { printf("arr[i] is %c
",arr[i]); i++; } return 0; }


Ответ

Это не имеет никакого отношения к языку С, а зависит только от алгорима обработки комбинации Ctrl-Z консолью Windows и интерпретацией результатов этой обработки той реализацией стандартной библиотеки, которую вы используете.
Ввод в Windows терминале буферизуется построчно. При этом обработка присутствующих в буфере символов ^Z следует довольно запутанному алгоритму (по крайней мере при использовании стантартной библиотеки из комплекта MSVC).
Комбинация Ctrl-Z сама по себе не вызывает "проталкивания" накопленного буфера на выход (в отличие от комбинации Ctrl-D в Linux). Она лишь добавляет во входной буфер символ ^Z, т.е. \x1a. Вы можете нажать Ctrl-Z несколько раз, помещая во входной буфер несколько символов ^Z. После этого вы можете продолжать вводить что-то еще. Чтобы все-таки послать накопленный буфер ожидающему процессу, придется нажать Enter Если входной буфер содержит какие-то символы до первого появления символа ^Z, то ожидающий ввода процесс увидит все эти символы, после чего процесс увидит один символ ^Z, т.е. \x1a. Это будет просто символ \x1a. Никакой ситуации "конец файла" при этом не возникнет. Однако остаток входного буфера (после первого символа ^Z) процессу виден не будет, как будто его и не было.
То есть если вы введете в Windows терминале последовательность abc^Z^Zdef^Zghi и нажмете Enter, то на вход ваш процесс получит символы a, b, c и \x1a. Весь остальной ввод пропадет бесследно. Заметьте, что при этом "пропадает" и символ перевода строки, cгенерированный нажатием Enter Если входной буфер сразу же начинается с символа ^Z, то входной буфер считается пустым. Все его содержимое пропадает, не происходит даже чтения символа ^Z. Возникает ситуация "конец файла".
То есть если вы введете в Windows терминале последовательность ^Zdef и нажмете Enter, то на вход ваш процесс не получит вообще ничего. Вместо этого функция ввода сообщит вам, что наткнулась на конец файла.
Поэтому для того, чтобы создать в буферизованном консольном вводе ситуацию "конец файла" придется вводить ^Z в самом начале новой строки.

Если вам нужна посимвольная обработка входа, то можно предварительно отключить построчную буферизацию ввода
HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); DWORD dwMode; GetConsoleMode(hIn, &dwMode); dwMode &= ~ENABLE_LINE_INPUT; SetConsoleMode(hIn, dwMode);
В таком варианте каждый введенный символ будет немедленно читаться вашей getchar() и символ ^Z будет немедленно интерпретироваться как конец файла.

четверг, 11 октября 2018 г.

Работа с stdin и stdout дочернего процесса одновременно в lua

При написании lua скрипта наткнулся на одну вещь: io.popen, судя по документации не может передать одновременно дескрипторы stdin и stdout запускаемого процесса.
Пример проблемного кода:
cmdexecute = function(cmd, input) local f = io.popen(cmd, "w"); if tostring(input) ~= nil then f:write(tostring(input)); end; local l = f:read("*a"); f:close(); return (l); end;
if cmdexecute('xargs ps', 'aux') ~= nil then print 'ps aux что-то выдал'; else print 'ps aux промолчал'; end;
Результат выполнения:
Длинный-длинный вывод ps aux... ps aux промолчал
А должно быть по идее:
ps aux что-то выдал
Вопрос: как получить дескрипторы и stdin, и stdout из popen? Или, возможно, есть какое-то другое решение задачи, без io.popen?
Upd: по причине актуальности вопроса поясняю: решения с ffi допустимы


Ответ

Вам должен помочь модуль lua-subprocess
Функция subprocess.popen принимает последним аргументом таблицу с именованными опциями. Среди них есть опции stdin, stdout и stderr, куда можно передать строку (имя файла), число (системный дескриптор файла), файловый объект или специальную константу subprocess.PIPE, которая создаст пайп для этого потока и сохранит его в возвращаемом объекте.

вторник, 2 октября 2018 г.

printf как средство печати переменных в С

Я не знаю как точно сформулировать то, что я хочу спросить, но выглядит это следующим образом:
Как напечатать содержимое переменной в С
char msg = 'k'; printf("%c", msg); Как напечать содержимое переменной в С++
char msg = 'k'; cout << msg;
А теперь вопросы:
В C++ для вывода не нужно указывать модификатор, тогда почему в С нет аналога cout (я все понимаю, что язык гораздо старше C++, но все же)? Мне часто пишут, что тип в printf нужно приводить:
char msg = 'k'; printf("%f", (float)msg);
А зачем? В аналоге С++, этого же не надо делать! Почему я не могу написать так: printf("%f", msg);? В чем причина? Разве printf не приведет данные самостоятельно к тому модификатору, который мы указываем?


Ответ

В C, printf(format, msg) в исходном коде вызывает одну и ту же функцию вне зависимости от типа, значения msg переменной. C — статически типизированный язык, это значит, что во время исполнения printf функция не знает, что msg это char, более того printf даже не знает сколько её аргументов передали. Поэтому приходится руками в format строке задавать желаемое представление: значение какого типа из памяти, в которой msg лежит, загрузить и как это значение отформатировать, чтобы получившиеся байты затем в stdout записать.
В C++, cout << msg для разных типов msg может скомпилировать вызов разных функций (перегрузки оператора <<). Вы легко можете определить свой оператор <<, к примеру, чтобы выводить векторы для отладки:
#include #include
template std::ostream& operator << (std::ostream& os, const std::vector& v) { for (auto&& x : v) os << x << ' '; return os; }
int main() { std::vector v {1, 2, 3}; std::cout << v << '
'; }
Пример:
$ g++ -std=c++11 main.cc -o main $ ./main 1 2 3
Упрощая, можно представить, что компилятор для cout << msg генерирует вызов print_char(msg), если msg это char, print_float(msg), если msg это float, print_vector(msg), если msg это vector итд. Здесь каждая функция знает какой тип она принимает и для каждого типа используется одно представление в виде байт по умолчанию, если iomanip не используются). К примеру, для int выводятся его десятичные цифры (а не hex или что-то другое) по умолчанию.
В C11 появился _Generic, который позволяет разные функции в зависимости от типа аргумента (controlling-expression) вызывать, поэтому можно определить print_arg(arg), которое бы работало для разных типов arg (один аргумент).
Почему я не могу написать так: printf("%f", msg);? В чем причина? Разве printf не приведет данные самостоятельно к тому модификатору, который мы указываем? ... printf("%f", (float)msg); \\ 107.000000, а printf("%f", msg); \\ 0.000000 почему ответ разный?
При вызове вариативных (variadic) функций таких как printf, объявленных с многоточием (...) в параметрах, которые могут принимать разное количество аргументов c неизвестными компилятору ожидаемыми типами (С компилятор не обязан понимать язык printf format-строки и значение format может быть вообще неизвестно во время компиляции), происходят преобразования по умолчанию: char неявно в int превращается (integer promotion) или в unsigned int, если значение char не представимо в int на данной платформе (экзотика). Аналогично, float в double превращается при вызове printf() — поэтому не требуется %lf для double в printf(), а используется просто %f
Для случая, c printf можно использовать простую модель: компилятор кладёт аргументы (int, double, etc) в область памяти, а printf читает их оттуда в соответствии с инструкциями в format строке — printf выступает в роли мини-компьютера: format задаёт программу и printf читает память (va_arg), в которой её аргументы лежат, форматирует их и записывает получившиеся байты в stdout
(double)msg объект может отличаться в памяти от (int)msg объекта. Поэтому printf может видеть разные битовые паттерны в памяти и соответственно, результат printf("%f", (int)msg) и printf("%f", (double)msg) могут быть разными (одна и та же инструкция: %f применяется к разному содержимому в памяти).
Пример для моей машины (как типы выглядят в памяти может зависеть от платформы (операционной системы + процессор) и опций компилятора). Байты в памяти я буду в виде hexdump показывать (к примеру: 6B16 == 10710).
Для printf("%c", msg)
'k' в C имеет тип int — 6B 00 00 00 char msg — 6B в printf это передаётся как int — 6B 00 00 00 %c берёт этот аргумент 6B 00 00 00 и превращает его в unsigned char (6B) и соответствующий байт (6B) выводится в stdout. См. printf документацию для c формата
Для printf("%f", (float)msg)
msg (6B) преобразуется в (float)msg — 00 00 D6 42 что передаётся в printf в виде double — 00 00 00 00 00 C0 5A 40 %f интерпретирует память 00 00 00 00 00 C0 5A 40 как число с плавающей точкой и выводит в фиксированном формате (6 знаков после запятой): 107.000000 (символ для точки от локали может зависеть). Соответствующие байты в ascii кодировке, которые в stdout пишутся: 31 30 37 2e 30 30 30 30 30 30
Для printf("%f", msg)
msg (char — 6B) передаётся в printf в виде int — 6B 00 00 00 %f интерпретирует память 6B 00 00 00 XX XX XX XX как число с плавающей точкой 5.3e-322 (если XX == 00) и выводится 0.000000
Так как при неверном формате поведение не определено (undefined behavior — UB), то printf("%f", msg) может сделать всё что угодно, хоть ракеты запустить. На моей машине результат printf("%f", msg) зависит от предыдущего кода к примеру:
char c = 'k'; printf("%f
", (float)c); printf("%f
", c);
печатает:
107.000000 107.000000 # XX XX XX XX == 00 C0 5A 40 (остатки от предыдущего вызова)
но:
printf("%f
", 5.3e-322); printf("%f
", c);
печатает:
0.000000 0.000000 # XX XX XX XX == 00 00 00 00 (остатки от предыдущего вызова)
Убедитесь, что для форматов известных во время компиляции (к примеру "%f
") ваш компилятор генерирует предупреждения для неверных типов — UB следует избегать.
printf("%i", (int)msg); \\\ 107 и printf("%i", msg);
в обоих случая msg char передаётся как int (6B 00 00 00) и при одинаковых форматах, результат должен быть одинаковым (6B16 == 10710).
Можно посмотреть содержимое переменных на вашей машине с помощью С кода:
#include
static void print_memory(unsigned char *memory, size_t n) { for (unsigned char *p = memory; p != memory + n; ++p) printf("%02X ", *p); puts(""); }
int main(void) { char c = 'k'; print_memory((unsigned char *)&c, sizeof c); int i = c; print_memory((unsigned char *)&i, sizeof i); float f = c; print_memory((unsigned char *)&f, sizeof f); double d = f; print_memory((unsigned char *)&d, sizeof d); }
Пример:
$ gcc -std=c99 main.c -o main $ ./main 6B 6B 00 00 00 00 00 D6 42 00 00 00 00 00 C0 5A 40
Как double в памяти может выглядеть
Пример из ответа, применённый к 107.0: число двойной точности в IEEE 754 формате представляется как d = ±знак · (1 + мантисса / 252) · 2порядок − 1023
знак, мантисса и порядок упакованы в двоичном представлении как:
00 00 00 00 00 C0 5A 40 // little-endian (8 byte as hex) 40 5A C0 00 00 00 00 00 // big-endian 0100000001011010110000000000000000000000000000000000000000000000 # 64-bit ^ |-самый левый бит знак= 0 (положительный)
^ ^ |---------| Затем 11 бит порядок= 0b10000000101 (==1029) ^ ^ |--------------------------------------------------| Оставшиеся 52 бита манитисса= 0b1010110000000000000000000000000000000000000000000000 = 3025855999639552 = 0xac00000000000
Всё вместе:
d = +(1 + 3025855999639552 / 252) * 2(1029 - 1023) = 64 + 3025855999639552 / 70368744177664 = (4503599627370496 + 3025855999639552) / 70368744177664 = 7529455627010048 / 70368744177664 = 107.0
Это демонстрирует, почему 107.0 может быть представлено в памяти как 00 00 00 00 00 C0 5A 40
Пример урезанной printf функции
#include // va_list, va_arg()
// emulate some printf() functionality using writec() static void print(const char* format, ...) { va_list args; va_start(args, format); int infmt = 0, is_char = 0; union { int i; double f; } arg; char buffer[23] = {0}; // enough for %c, %d, %f
for (const char *p = format; *p; ++p) { if (infmt) { // print arg infmt = 0; switch(*p) { case '%': // "%%": print % literally writec(*p); break; case 'c': // "%c": print char is_char = 1; // fall through case 'd': // "%d": print int case 'i': // "%i": print int arg.i = va_arg(args, int); // load int arg if (is_char) { is_char = 0; writec((unsigned char)arg.i); // format as char, write } else { itoa(arg.i, buffer, sizeof buffer); // format as int for (char *pb = buffer; *pb; ++pb) writec(*pb); // write } break; case 'f': arg.f = va_arg(args, double); // load double arg ftoa(arg.f, buffer, sizeof buffer); // format as floating point for (char *pb = buffer; *pb; ++pb) writec(*pb); // write break; default: arg.i = va_arg(args, int); // load int arg scpy(buffer, ""); for (char *pb = buffer; *pb; ++pb) writec(*pb); // write }; } else if (*p != '%') { // print literally writec(*p); } else { // *p == '%' infmt = 1; } } va_end(args); }
switch используется, чтобы распознать описатели преобразований (%d) в format-строке va_arg() загружает аргументы нужного типа вспомогательные функции itoa() и ftoa() форматируют int и double соответственно writec() пишет один байт в stdout
Это определение print() достаточно для кода:
int main(void) { char msg = 'k'; print("%c %i %i %f %x
", msg, msg, (int)msg, (float)msg, msg); print("%c ", msg); print("%i ", msg); print("%i ", (int)msg); print("%f
", (float)msg); print("%f
", msg); // XXX UB }
Пример:
$ cc -std=c99 print-example.c -o print-example $ ./print-example k 107 107 107.000000 k 107 107 107.000000 107.000000
Чтобы скомпилировать, достаточно вспомогательные функции определить (определения перед print() нужно вставить):
#include // POSIX write()
static void writec(unsigned char c) { write(1, &c, 1); }
static void scpy(char* dest, const char* src) { while (*dest++ = *src++); }
static void ftoa(double d, char* buffer, int n) { if (d == 107) //XXX scpy(buffer, "107.000000"); else scpy(buffer, "XXX"); }
/// format positive int as decimal ascii digits static char* utoa_rec(unsigned i, char* buffer, int *pn) { if (i >= 10) buffer = utoa_rec(i / 10, buffer, pn); if ((*pn)-- > 0) *buffer++ = '0' + (i % 10); return buffer; }
static void itoa(int i, char* buffer, int n) { if (i < 0) { i = -i; //XXX ignore INT_MIN if (n-- > 0) *buffer++ = '-'; // sign } buffer = utoa_rec(i, buffer, &n); if (n > 0) *buffer++ = '\0'; }
Определения функций приведены, чтобы можно было пример запустить, но фактически они просто заглушками являются (не для повторного использования), чтобы только продемонстрировать одну из простейших print(format, ...) реализаций.
Вот пример полной реализации vfprintf() из glibc