Deutsch
Germany.ruФорумы → Архив Досок→ Программирование

как победить sprintf ?

503  1 2 все
anly коренной житель26.05.17 03:58
anly
NEW 26.05.17 03:58 
Последний раз изменено 26.05.17 04:01 (anly)

в проэкте встречается много sprintf который, как известно, принимает любые аргументы и в любом количестве там где три точки: sprintf(char* buffer, const char* format, ...);


Беда в том что у одного из аргументов (который в троеточии) поменялся тип. Например, раньше был const char*, а стал std::string.


Студия 2015 дает хоть предупреждение в этом случаее, а вот Студия 2013 почему-то не даёт! Опции проэктов сравнивал - аналогичные. Программа просто вылетает при выполнении. Вот думаю как гарантированно найти все такие места?


Вариант конечно есть, но трудоёмкий: не использовать sprintf вообще, а поиском/заменой заменить на MySprintf, которых сделать кучу на все используемые в проекте случаи, без трёх точек.

Но может есть проще вариант?

Проклят нарушающий межи ближнего своего (Втор.27:17)
#1 
anly коренной житель26.05.17 11:09
anly
NEW 26.05.17 11:09 
в ответ anly 26.05.17 03:58

решил таки задачку более менее приемлемым способом:

- использование своей функции (меняем поиском/заменой) + несколько темплейтов

теперь компилятор ругается, если в список (который раньше был троеточием) попадает тип, что я не указал.


template<class T> void check_t(T) { wrong type!!!; } // здесь ругается компайлер

template<> inline void check_t(const char*) {}

template<> inline void check_t(char*) {}

template<> inline void check_t(int) {}

template<> inline void check_t(long) {}

template<> inline void check_t(unsigned long) {}

template<> inline void check_t(unsigned long long) {}

template<> inline void check_t(unsigned) {}

template<> inline void check_t(double) {}

template<> inline void check_t(char) {}

template<> inline void check_t(unsigned char) {}


template<class T>

void sprintf_my(char* buf, size_t size, const char* format, T t)

{

check_t(t);

sprintf_s(buf, size, format, t);

}


..... до шести аргументов бывает .....


template<class T, class T2, class T3, class T4, class T5, class T6>

void sprintf_my(char* buf, size_t size, const char* format, T t, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)

{

check_t(t);

check_t(t2);

check_t(t3);

check_t(t4);

check_t(t5);

check_t(t6);

sprintf_s(buf, size, format, t, t2, t3, t4, t5, t6);

}

Проклят нарушающий межи ближнего своего (Втор.27:17)
#2 
BorisL0 знакомое лицо26.05.17 18:49
NEW 26.05.17 18:49 
в ответ anly 26.05.17 03:58

Вообще-то стандартный Си-шный sprintf и не должен понимать std::string. Самое правильное, заменить все переменные std::string s в sprintf на s.c_str(), который и преобразовывает с++ string в си-шную строку.

#3 
anly коренной житель26.05.17 19:49
anly
NEW 26.05.17 19:49 
в ответ BorisL0 26.05.17 18:49, Последний раз изменено 26.05.17 20:08 (anly)

да нет, как раз недостаток функций с тремя точками именно есть - отсутствие контроля типа. Т.е. туда передать можно всё что угодно.

поэтому

sprintf(buf, "%s", std::string());

прекрасно компилируется, но приводит к вылету при выполнении.


Кстати в некоторых случаях подобный код работает без вылета. А точнее такой:

sprintf(buf, "%s", CString());

и работает он только потому что внутри себя CString самыми первыми данными (а в релизе других и нет) располагает сам указатель на стринг. Ну и еще одно условие успешной работы - этот CString должен быть последним в списке, ибо после него имплементации sprintf-а уже невозможно вычислить адрес следующего параметра (ведь %s для sprintf-а означает что следующий будет через 4 байта, а не через sizeof(CString), о размере которого он понятия не имеет).


Самое правильное, заменить все переменные std::string s в sprintf на s.c_str()

это верно, но в достаточно большом проекте это нереально. А вот с описанным выше способом - реально, причем сам компилятор покажет где это нужно подправить.


Кстати для чистоты описанный выше способ можно заключить в #ifdef _DEBUG. Хотя по идее в релизе компайлер и сам оптимизирует инлайн функции, а пустые как check_t вообще выбросит.

Проклят нарушающий межи ближнего своего (Втор.27:17)
#4 
Murr_0002 постоялец03.06.17 19:58
Murr_0002
NEW 03.06.17 19:58 
в ответ anly 26.05.17 19:49

Еще один вариант - помнить в каком порядке линкуются обьектники в Сях.

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

Т,е, взяв имплементацию спринтf() из какой-нибудь библиотеки и добавив контроль типов получишь что надо.

У тебя вроде микс - С/Срр - но там отличий нет.

#5 
anly коренной житель04.06.17 09:33
anly
NEW 04.06.17 09:33 
в ответ Murr_0002 03.06.17 19:58, Последний раз изменено 04.06.17 09:50 (anly)
взяв имплементацию спринтf() из какой-нибудь библиотеки и добавив контроль типов получишь что надо
а как добавить контроль типов?


На мой взгляд, добавить контроль типов возможно только написав совершенно новую функцию, т.е. с другими аргументами. Как я и сделал выше, заменив три точки реальными типами.


Решение выше можно слегка улучшить. А именно не делать новое имя sprintf_my, а использовать namespace:


namespace XX

{

template<class T>

void sprintf_s(char* buf, size_t size, const char* format, T t)

{

check_t(t);

::sprintf_s(buf, size, format, t);

}

}

using namespace XX;


это позволит обойтись без переименования каждого вызова в уже существующем коде.

(Правда и недостаток есть: можно забыть сделать #include "my_sprintf.h")

Проклят нарушающий межи ближнего своего (Втор.27:17)
#6 
Murr_0002 постоялец04.06.17 12:39
Murr_0002
NEW 04.06.17 12:39 
в ответ anly 04.06.17 09:33

а как добавить контроль типов?

-----

В твоем случае нужно проверять куда указывает указатель при спецификаторе %s - сегмент данных или сегмент кода/стека.

#7 
anly коренной житель04.06.17 15:56
anly
NEW 04.06.17 15:56 
в ответ Murr_0002 04.06.17 12:39
проверять куда указывает указатель при спецификаторе %s - сегмент данных или сегмент кода/стека.
а толку?
Проклят нарушающий межи ближнего своего (Втор.27:17)
#8 
Murr_0002 постоялец04.06.17 18:36
Murr_0002
NEW 04.06.17 18:36 
в ответ anly 04.06.17 15:56

а толку?

-----

При

char*

будет указатель в сегмент данных, а для

std::string

указатель должен быт' в сегмент кода.


Как обычно - зависит от системы, то что Я помню относится к Борланду.


#9 
anly коренной житель04.06.17 19:53
anly
NEW 04.06.17 19:53 
в ответ Murr_0002 04.06.17 18:36, Последний раз изменено 04.06.17 19:54 (anly)

всё равно толку не будет.

В случае std::string, там вообще никаких указателей не будет, т.к. лежать в стеке будет целый экземпляр класса, а что там за данные у него внутри - кто знает? И к тому же std::string это лишь к примеру было, могут быть и другие классы.


Но самый главный аргумент, что бестолку, это то, что ошибку надо найти на этапе компиляции, а не выполнения.

Проклят нарушающий межи ближнего своего (Втор.27:17)
#10 
Murr_0002 постоялец05.06.17 12:39
Murr_0002
NEW 05.06.17 12:39 
в ответ anly 04.06.17 19:53, Последний раз изменено 05.06.17 12:45 (Murr_0002)

лежать в стеке будет целый экземпляр класса

-----

Ну так тебе этого достаточно - не в сегменте данных - класс - надо разбираться какой и где реальные данные.


ошибку надо найти на этапе компиляции

-----

Да, это будет удобнее. Хотя и не всегда возмозhно.

#11 
anly коренной житель05.06.17 19:35
anly
NEW 05.06.17 19:35 
в ответ Murr_0002 05.06.17 12:39
Ну так тебе этого достаточно - не в сегменте данных - класс - надо разбираться какой и где реальные данные.

так всё равно нет возможности узнать что там находится. В стеке будут какие-то числа. Которые могут быть любыми. Там может быть даже "Hello World" прямым текстом без указателей. Числа могут быть похожими и на указатели (ими не являясь) хоть на данные, хоть на код.

Проклят нарушающий межи ближнего своего (Втор.27:17)
#12 
Murr_0002 постоялец05.06.17 20:34
Murr_0002
NEW 05.06.17 20:34 
в ответ anly 05.06.17 19:35

В стеке будут какие-то числа. Которые могут быть любыми.

-----

В Сях - не совсем любые.

В Сях для спецификатора %s на вершине стека (в зависимости от реализации - с порядковым смещением) должен быть указатель на строку. Т.е. адрес в сегменте данных.


прямым текстом без указателей.

-----

Мы таки говорим об Сях? Или об чем-то другом?

В тех реализациях которые Я помню - там ожидается указатель.


#13 
anly коренной житель05.06.17 22:55
anly
NEW 05.06.17 22:55 
в ответ Murr_0002 05.06.17 20:34
В Сях для спецификатора %s на вершине стека (в зависимости от реализации - с порядковым смещением) должен быть указатель на строку.
компилятор для sprintf никак это не контролирует. Там может даже ничего не оказаться вообще (т.е. мусор), а может и - любой тип.
Я помню - там ожидается указатель.
sprintf то ожидает, но в том то и проблема что программист туда загнал не то что sprintf ожидает, и задача именно в том чтобы это распознать до запуска программы на выполнение.
Проклят нарушающий межи ближнего своего (Втор.27:17)
#14 
Murr_0002 постоялец05.06.17 23:31
Murr_0002
NEW 05.06.17 23:31 
в ответ anly 05.06.17 22:55

никак это не контролирует.

-----

Ну разве Я с этим где-то спорю? Разумеется, количество и типы параметров при передаче по элипсису никак не контролируется.


проблема что программист туда загнал не то

-----

Именно это Я тебе и предлагаю отследить. В ран-тайме. Бо, не всегда можно сделать на уровне компиляции. Про void** помнишь?

#15 
anly коренной житель06.06.17 21:46
anly
06.06.17 21:46 
в ответ Murr_0002 05.06.17 23:31, Последний раз изменено 06.06.17 22:04 (anly)
Именно это Я тебе и предлагаю отследить. В ран-тайме
в солюшине 60 проэктов и почти 8 тысяч sprintf-ов. Да мне жизни не хватит так оттестить, чтоб заставить всех их по разу выполниться.


Кстати я уже заимплементил, попутно заменяя sfrintf на sprint_s везде где возможно (точнее компайлер сам мне показал все места) и штук 5 баг с неверным типом в трех точках выловил. Конечно баги древние, и возможно в мертвом коде. Но зато теперь не нужно бояться за новую багу, если где тип поменяется.

Проклят нарушающий межи ближнего своего (Втор.27:17)
#16 
Murr_0002 постоялец07.06.17 21:07
Murr_0002
NEW 07.06.17 21:07 
в ответ anly 06.06.17 21:46

попутно заменяя sfrintf на sprint_s везде где возможно

-----

Ничего не понял.

Есть стандартная библиотека - содержит спринтф().

Написанный код, любой код в проекте, имеющий реализацию спринтф() будет использоваться вместо библиотечного.

Зачем нужна спринт_с()?


Да мне жизни не хватит так оттестить

-----

Потому и предлагается обработка по месту.

#17 
Murr_0002 постоялец07.06.17 21:10
Murr_0002
NEW 07.06.17 21:10 
в ответ Murr_0002 07.06.17 21:07, Последний раз изменено 07.06.17 21:11 (Murr_0002)

P.S.

Точнее - если ссылка на функцию уже разрезолвлена - все последующие источники игнорируются.

#18 
anly коренной житель07.06.17 22:59
anly
NEW 07.06.17 22:59 
в ответ Murr_0002 07.06.17 21:07, Последний раз изменено 07.06.17 23:02 (anly)
Зачем нужна спринт_с()?
sprintf_s тоже библиотечная, но безопаснее чем sprintf, т.к. проверяет размер буфера.

И если раньше был такой вызов

sprintf(buf, format, a, b, c)

где буфер был объявлен как char buf[123];

то можно прото заменить на

sprintf_s(buf, format, a, b, c)

и всё скомпилируется.

Но вот если буфер был объявлен как char* buf; то так просто не получится.

Проклят нарушающий межи ближнего своего (Втор.27:17)
#19 
anly коренной житель07.06.17 23:00
anly
NEW 07.06.17 23:00 
в ответ Murr_0002 07.06.17 21:07
Потому и предлагается обработка по месту.
ну да, только лажу скорее всего первым обнаружит заказчик, что неприятно.
Проклят нарушающий межи ближнего своего (Втор.27:17)
#20 
1 2 все