Зачем второй параметр, FILE *stream -зачем нужен?.
Но без этого параметра компилсятор выдает предупреждение вот такого формата: Warning 1 initialization from incompatible pointer type Почему так происходит? Буду рад даже ссылки на подобную проблему.
не совсем понял, т.к документация на англ. но. static FILE stdout_LCD = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE ); в документе приводится пример создания этого потока, т.е в скобках я указал uart_putchar - это функция которой будет вызываться на отправку символа, потом следует NULL т.к я не буду принимать символ , ну и параметр _FDEV_SETUP_WRITE говорит на то что я буду использовать поток только для отправки символа. но зачем я вот этого не пойму при объявлении функ. надо писать FILE *stream??? uart_putchar(char c, FILE *stream) { тут функция что-то делает return 0; } Кстати когда я объявляю функцию uart_putchar(char c, FILE *stream) таким способом то уже вызывать функцию я должен только printf -ефом, а вызвать ее без printf т.е так uart_putchar (simvol) , компилятор выдаст ошибку.
сталкивались, но с IBM, а не AVR. Здесь как-то все немного по другому и на мой взгляд, потоки в АВР - чрезмерное усложнение, ведущее исключительно к увеличению ресурсозатрат. Лучше разберитесь со всем этим на уровне регистров.
Да согласен что в IBM немножко по другому, и согласен что в avr использовать потоки затратно, но очень хотелось бы понять суть! Зачем этот второй параметр? я про FILE *stream, я от этого не могу понять. Как-то странно. Когда я передаю параметр в функцию то я его и ловлю,передаю два , и соответственно я этих два параметра ловлю ну например main() { char aa=1,bb=2; function(aa,bb); } void function (char a, char b) { тут что-то делаем; } а с потоком мне не понято
Чисто логически можно предположить следующее. Простой C - это процедурный язык. Когда некоторый набор функций принадлежит какому-то общему "объекту", то раньше во всех функциях добавляли указатель на этот объект, чтобы в любой из них можно было получить данные об окружении текущей операции, тем более, когда предполагается последовательность операций.
Представьте себе мк с 10 USART и для каждого идёт работа при помощи prinntf(). Как привязать один printf() к конкретному порту? Вот, думается мне, для этого и есть указатель на некоторый объект. Нужно читать заголовочники, где все эти штуки описаны.
Итак, у нас 10 портов и мы хотим поочереди записать в каждый порт "Hello world!", как это можно сделать при помощи printf() и одного стандартного "потока"? На самом деле это структуры типа FILE.
Вариант 1. Использовать глобальную переменную, которая бы соотв-вала бы номеру текущего порта для вывода. Перед очередным использованием printf() менять эту переменную, а в put_char() следить за глобальной переменной.
Вариант 2. Использовать поле udata (user defined and accessible data) структуры stream, которая имеет тип FILE. Т.е., поскольку стандартный вывод у нас соотв-ет переменной stdout, то мы можем обратиться к полю stdout->udata, записать туда номер текущего порта для вывода, а потом проверять stream->udata внутри put_char(). Я не проверял, но, видимо, второй параметр будет инициализирован ссылкой на stdout.
Может быть я чуть запутанно поясняю, но смысл в том, чтобы иметь сквозной объект для хранения текущих параметров при выполнении ряда связанных операций. Сейчас в больших ЯВУ изменилась идеология и нет необходимости иметь ссылку на объект, т.к. сам объект содержит в своём описании всё, что нужно для работы его методов.
Т.е. "поток" используется, но это неявно. Поскольку функции универсальные, то этот параметр используется для универсальности.
П. С. Т.е. это this, только для процедурного языка. Если вы программировали на c#, vb.net или чем-то подобном, то знаете, что внутри метода можно обратиться к объекту-хозяину через указатель this. Так вот это он самый и есть, только для сущности, которая у нас тут называется FILE. И stream - это указатель на текущий экземпляр FILE.
да с несколькими UART согласен. но есть еще один вариант правда экзотический с несколькими потоками, ну например. есть 2 uart. первый uart будет стандартный поток второй будет созданий. static FILE stdout_LCD = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE ); //для стандартного потока. static FILE stdout_LCD_num2 = FDEV_SETUP_STREAM(uart_putchar_num2, NULL, _FDEV_SETUP_WRITE ); ну потом main() { stdout=&stdout_LCD; // для того чтобы просто писать printf(); а не fprintf(stdout_LCD, и дальше что надо); printf("HELLO"); //для первого usart fprintf(uart_putchar_num2,"hello"); // для второго uart; } void uart_putchar_num2(char c, FILE *stream) { // тут драйвер-функция для второго uart. // недостаток этого что для каждого uart надо писать свою функцию, что очень не желательно для мк. Но как вариант можно-ли да. } если использовать printf() для lcd 1602 нам надо инициализировать дисплей (можно использовать для иниц. один поток, для отправки текста другой поток. Если честно по поводу udata надо разобрать более детально.(я ее не использовал ни когда) ---------- еще один пример : main () { int a=20; int b=10; my_func(b,&a); } void my_func (int temp, int *tmp) { // теперь переменная temp скопировалась, а переменная tmp ну я работаю с ней через адрес, и явного копирования не было. // тут можно что угодно ну например. *tmp=*tmp+temp; } но как видно я сделал так т.е. явно указал my_func(b,&a); а с потоком я не что такого не делал. как бы все авто. Значить в каких-то вариантах надо делать , т.е явно указывать пример с &a(я указывал всегда), а в каких то вариантах не надо, почему ???
По поводу udata есть даже комментарии в заголовочном файле и макросы для использования этого поля, которые я раньше не заметил (stdio.h):
Код:
/** This macro inserts a pointer to user defined data into a FILE stream object.
The user data can be useful for tracking state in the put and get functions supplied to the fdevopen() function. */ #define fdev_set_udata(stream, u) do { (stream)->udata = u; } while(0)
/** This macro retrieves a pointer to user defined data from a FILE stream object. */ #define fdev_get_udata(stream) ((stream)->udata)
Функция printf() неявно использует переменную stdout для своей работы, т.е. она делает то же, что и fprintf( stdout, ... ). Такое поведение определяется стандартом языка C. Есть три зарезервированных переменных типа указатель на FILE. Это:
Код:
/** Stream that will be used as an input stream by the simplified functions that don't take a \c stream argument.
The first stream opened with read intent using \c fdevopen() will be assigned to \c stdin. */ #define stdin (__iob[0])
/** Stream that will be used as an output stream by the simplified functions that don't take a \c stream argument.
The first stream opened with write intent using \c fdevopen() will be assigned to both, \c stdin, and \c stderr. */ #define stdout (__iob[1])
/** Stream destined for error output. Unless specifically assigned, identical to \c stdout.
If \c stderr should point to another stream, the result of another \c fdevopen() must be explicitly assigned to it without closing the previous \c stderr (since this would also close \c stdout). */ #define stderr (__iob[2])
__iob[] - это массив стандартных указателей на "описатели потоков". Есть некоторое множество функций, работа с которыми подразумевает вывод с использованием этих трёх переменных. Я точно не помню, но вроде бы в стандарте есть функция, которая выводит сообщение об ошибке в поток вывода stderr. Одна из таких - assert(). Можно приравнять stderr = stdout и использовать в коде assert() для вывода диагностических сообщений в случае ошибок в коде. Только я не знаю как она в случае мк будет отрабатывать.
Если вернуться к printf(), то можно под отладчиком посмотреть какое значение имеет второй параметр в функции вывода символа, при использовании printf() и сравнить это значение с stdout. Думаю, это будет один и тот же указатель на структуру FILE.
Что касается указания второго параметра, то в структуре FILE дан явно его вид:
Код:
/* * This is an internal structure of the library that is subject to be * changed without warnings at any time. Please do *never* reference * elements of it beyond by using the official interfaces provided. */ struct __file { char *buf; /* buffer pointer */ unsigned char unget; /* ungetc() buffer */ uint8_t flags; /* flags, see below */ #define __SRD 0x0001 /* OK to read */ #define __SWR 0x0002 /* OK to write */ #define __SSTR 0x0004 /* this is an sprintf/snprintf string */ #define __SPGM 0x0008 /* fmt string is in progmem */ #define __SERR 0x0010 /* found error */ #define __SEOF 0x0020 /* found EOF */ #define __SUNGET 0x040 /* ungetc() happened */ #define __SMALLOC 0x80 /* handle is malloc()ed */ #if 0 /* possible future extensions, will require uint16_t flags */ #define __SRW 0x0100 /* open for reading & writing */ #define __SLBF 0x0200 /* line buffered */ #define __SNBF 0x0400 /* unbuffered */ #define __SMBF 0x0800 /* buf is from malloc */ #endif int size; /* size of buffer */ int len; /* characters read or written so far */ int (*put)(char, struct __file *); /* function to write one char to device */ int (*get)(struct __file *); /* function to read one char from device */ void *udata; /* User defined and accessible data. */ };
При инициализации этой структуры нужно передавать функцию вывода символа с двумя параметрами:
Код:
int (*put)(char, struct __file *); /* function to write one char to device */
Если мы используем printf(), то инициализация этого параметра происходит при присвоении stdout какого-то значения. Если мы используем fprintf(), то инициализация происходит, когда мы в эту функцию передаём указатель на созданный поток явно.
предполагать не надо, можно быть уверенным в Си printf - это функция/обертка для упрощения потокового вывода в stdout, фактически же она обращается к другой СТАНДАРТНОЙ функции fprintf, которая уже может выводить в любой поток. поэтому функция вывода симвода принимает указатель на этот самый поток - это фактически предписано стандартом: уметь выводить в любой поток, а не только stdout.
в применении к МК пример был приведен: можно выводить в разные UART (в некоторых МК их по два и более), кроме того весьма популярно работать с SD-картами - это уже реальные файлы, а еще есть дисплеи... то, что в простеньких проектах это кажется лишним, не должно сильно пугать: оптимизатор наверняка все приведет в нормальное состояние без лишней избыточности. во всяком случае если вы уж решились использовать форматированный вывод, отжирающий минимум 1,5-2К памяти программ, то пара байт на указатель потока вас и подавно не должна смущать
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
Функция printf() неявно использует переменную stdout для своей работы, т.е. она делает то же, что и fprintf( stdout, ... ). Такое поведение определяется стандартом языка C. Есть три зарезервированных переменных типа указатель на FILE.
Да это верно, именно так. вот я и для этого делал: у меня был свой поток
//тут разные инклюды static FILE stdout_LCD = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE ); main() { // вот это же мой поток выделен красным stdout_LCD // и тут я присваиваю свой поток к stdout командой stdout=&stdout_LCD; stdout=&stdout_LCD; //ну потом можно уже print("hello"); // а не fprint("hello"); вот я же явно присвоил командой stdout=&stdout_LCD; чтобы не пришлось писать fprintf(); } с этим я согласен : "Функция printf() неявно использует переменную stdout для своей работы, т.е. она делает то же, что и fprintf( stdout, ... )." Конечно хочу поблагодарить всех кто отозвался на мой вопрос.
но не пойму я следующие: припустим я хочу через поток работать с lcd дисплеем и мне надо написать функцию-драйвер для printf(). т.е в строке static FILE stdout_LCD = FDEV_SETUP_STREAM(lcd_driver, NULL, _FDEV_SETUP_WRITE ); и вот тут при создании потока выделено красным я говорю что функция которая будет отвечать за отправку символа будет lcd_driver и вот тут следующие вопросы пример кода # include подключаем нужные файлы static FILE stdout_LCD = FDEV_SETUP_STREAM(lcd_driver, NULL, _FDEV_SETUP_WRITE); // вот тут lcd_driver это та функция которая отвечает за отправку символа или строки , но главное не это потом main() { print("hello"); } void lcd_driver(char c) { // функция отправляет символ, описывать функцию думаю не надо. } все можно сказать код рабочий за том исключением что тут нет функ. инициализации дисплея. ВОТ ТУТ САМОЕ ГЛАВНОЕ, ТАК КОМПИЛЯТОР РУГАЕТСЯ, Т.Е ЕСТЬ ВОРНИНГ Warning 1 initialization from incompatible pointer type но код работает, теперь я в этой функ. т.е void lcd_driver(char c) добавляю FILE *stream и получается void lcd_driver(char c, FILE * stream); ВСЕ ВОРНИНГА НЕТ КАК ЭТО ? и соответственно я не могу теперь использовать функ. lcd_driver напрямую только через printf() если вспомнить мой первый пост, т.е сам вопрос там есть пример кода только для uart то в функ. int uart_putchar(char c, FILE *stream) Я ЯВНО ЖЕ НЕ ИСПОЛЬЗУЮ FILE *stream , зачем тогда второй параметр. может я что-то не допонял. {
Дело в том, что C - язык типизированный и указатели на функции у него тоже типизированные. Это сообщение говорит о том, что вы инициализируете поле в структуре __file не тем типом указателя на функцию. Я уже приводил выше цитату из заголовочника:
Код:
int (*put)(char, struct __file *); /* function to write one char to device */
Макрос, который создаёт поток, на самом деле просто присваивает свои параметры полям структуры __file. Функция для записи определена своим типом как принимающая два параметра. Кроме того, она ещё должна возвращать int (посмотрите на пример из заголовочника, там функция описана правильно, согласно типа).
Если вы опишите функцию по-другому, то это приведёт к нарушению в работе со стеком (странно, что вообще такое компилятор пропустил). Эта проблема называется - нарушение баланса стека и приводит к непредсказуемому поведению программы. Ни в коем случае нельзя так делать, как у ваш выше в примере. put_char() должна возвращать int и должна быть определена с двумя параметрами.
А, если что-то там и так "заработало", то это на свой страх и риск.
lcd_driver() во втором случае можно использовать и напрямую, нужно просто передать NULL в качестве второго параметра.
Мы наверное про разный второй параметр говорим. static FILE stdout_LCD = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE ); я не про NULL говорю
Я просто хочу понять зачем в void lcd_driver(char c, FILE * stream); надо писать именно в этой функ. второй параметр FILE *stream
Я просто хочу понять зачем в void lcd_driver(char c, FILE * stream); надо писать именно в этой функ. второй параметр FILE *stream
а почему вы, например, не интересуетесь, почему функция int tolower(int c);, которая служит для преобразования СИМВОЛОВ в качестве параметра и результата использует тип int, а не char, что как бы более логично?
вот так решили разработчики стандарта - такой ответ вас не устроит?
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
Мы наверное про разный второй параметр говорим. static FILE stdout_LCD = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE ); я не про NULL говорю
Я просто хочу понять зачем в void lcd_driver(char c, FILE * stream); надо писать именно в этой функ. второй параметр FILE *stream
Вот и я по прошествии пяти лет с последнего сообщения хочу понять.
делаю проект на atmega 2560 в составе arduino mega 256. загрузчик снес. пишу на СИ и ассемблере. приладил дисплей. вывод букв и графики писал сам на ассемблере, потом обернул все в СИ-шную функцию и дальше на СИ делаю. сначала пользовался строковой библиотекой: itoa и тп. чтобы строки выводить. Сейчас дошел, что можно выводить через printf и тп. на экран(и в порт через USB консолью любой вывод получать), а переменные сама функция будет преобразовывать, что гораздо удобней. Функцию вывода строк на экран я писал еще до плотного знакомства с printf, поэтому сейчас её надо переделать. т.к. моя функция принимает строку с терминальным нулем, координаты курсора, цвет символа, цвет фона. И вот, чтобы не городить огород очередной раз я понял (предварительно) что функция моя должна выводить один символ. а данные о остальных (вышеуказанных) параметрах должны храниться в udata. Получается очень удобно, эти параметры будут храниться в статичной структуре, между вызовами printf никуда не пропадать! а моя переписанная функция вывода посимвольная будет ориентироваться на эти данные и, когда курсор дойдет до конца строки, переносить (или не переносить а сдвигать весь экран влево, например) курсор. так как там указатель типа воид, туда я забью адрес своей структуры, где вся эта красота будет храниться. А вот другие поля структуры FILE ведь тоже как то можно использовать?
Код:
char *buf; /* buffer pointer */ unsigned char unget; /* ungetc() buffer */ и int size; /* size of buffer */ int len; /* characters read or written so far */
знает кто-нибудь?
вот эти поля для записи адресов функций для вывода/ввода символов на/из устройство и, понятное дело, их нам менять не надо на ходу:
Код:
int (*put)(char, struct __file *); /* function to write one char to device */ int (*get)(struct __file *); /* function to read one char from device */
а вот к флагам можно и наверное подразумевается что мы можем обращаться? видел кто нибудь инфу про это?
я бы не рекомендовал лезть в эту структуру, поскольку она используется в первую очередь для внутренних нужд lib-c и, если читать из неё вы можете безбоязненно, то писать в неё лучше не надо
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
не согласен. иначе зачем фунцкция вывода символа объявлена самими составителями библиотеки с приемом параметра- адреса этой структуры. Однозначно, чтобы пользователь использовал эти данные каким-либо образом. /* * This is an internal structure of the library that is subject to be * changed without warnings at any time. Please do *never* reference * elements of it beyond by using the official interfaces provided. */ перевод: Это внутренняя структура библиотеки, которая может быть изменена без предупреждения в любое время. Пожалуйста, никогда не ссылайтесь на его элементы, кроме как с помощью предоставленных официальных интерфейсов.
одно можно с уверенностью сказать что поле: void *udata; /* User defined and accessible data. */
указатель типа воид, то есть для хранения пользователем адреса чего угодно. и в библиотеке есть функция для обращения к этим данным, и туда я запишу адрес структуры, поля которой, в свою очередь, будут хранить данные о позиции курсора, цвете символа и фона
за остальные поля не знаю, нет примеров в интернете, во всяком случае я не нашел. у всех функция эта не использует *stream никак. Кстати как рекомендация тем кто пишет подобное, если вам структура не нужна, все равно придется ее указывать в определении вашего варианта. если уж объявлена она ранее, то указывайте её. другое дело - функция имеет право никак не использовать этот параметр.
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 16
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения