Злосчастный код, который похитил у меня несколько часов времени.
char c='0';
string str="Text"+c;
Что будет лежать в str? Отнюдь не "Text0" как можно было бы ожидать.
Здесь к адресу строки "Text" добавляется 48 байт. И конструктор str получает указатель на эту область памяти. По несчастному стечению обстоятельств в этой области памяти у меня была строка "RESTART". И вот с этого момента начались удивительные приключения этого RESTART'а, которые в итоге привели к обращению по нулевому указателю, причины которого мне и пришлось раскапывать.
Компиляторы, которые я смотрела - CodeWarrior и Visual Studio 2005 даже ворнинга в этом случае не дают.
57 коммент.:
Это еще раз подтверждает тот факт, что когда работаешь на C++ не стоит связываться с примитивными типами. :)
string str = string("Text") + c;
ИМХО сработает как надо, хотя не проверял. Надо вырабатывать у себя привычки работать с объектами.
PS: Ни в коем случае не хочу никого обидеть.
это еще раз подтверждает тот факт, что перегруженные операторы C++ вырабатывают дурацкие привычки.
а потом за такими код вычищать неделями приходится.
нудык ктож к const char * прибавляет char ?
))))
Соглашусь с waker. Я вообще на plain C пишу ;-)
GCC может быть предупредил бы.
Еще Студия, не выдает предупреждения если написать что-то вроде
printf("%s", myClass);
GCC же в этом случае говорит почти по-русски, что тут использовать не POD-типы нельзя.
Я согласен, кстати с Андреем. Еще лучше не использовать типы int или short или long, а использовать что-то вроде Int16, UInt32, size_t, и т.п.
2arkanoid: Ну дык тем, кто пишет на си, никогда не придет в голову складывать строки. :)
А С++ предоставляет для этой цели ИМХО достаточно удачную абстракцию. И это балует. :)
string text = "Text";
string srt = text + c;
тоже не создал бы проблем, но тяжелое наследие си, в виде арифметики над указателями, мешает это интуитивно совместить. :)
2waker: как объединяют строки типа string у вас?
Я еще раз порадовался, что перешел на PHP/Javascript.
Велик и могуч C++, но чем более он велик, тем больше возможность совершить подобную ошибку
gcc тоже никак на это не ругается.
Для любого компилятора что char что int - одно и тож.
2naishin: Я не очень люблю замещать long, int, short - последний нужен крайне редко, а первые два и так достаточно понятны. Хотя это имеет смысл при настоящем кроссплатформенном программировании.
Я, конечно, дико извиняюсь, но возможностей набагать в PHP примерно столько же, сколько и в C++, то есть немеряно.
недавно был очень похожий случай.
есть функция:
string addsign(char sign, const string& s)
{
1) return sign+s; // так нельзя
2) return string(s1)+s; //тоже нельзя
3) return string(s1, 1)+s;// только так можно... но блин не сразу допетриваешь
}
Если очень хочется писать "Text"+c, лучше определить враппер над char'ом и обозвать его, скажем, ANSI_CHAR.
Затем добавить глобальный оператор который принимает строку (на стеке) как lhs и конст-реф ANSI_CHAR как rhs.
Тогда код string str="Text"+c; превратится в создание строки с текстом "Text" и вызов нашего оператора +.
А использование голых си-строк запретить в кодинг стандарте и жестоко бить нарушителей :)
правда, вышеописаный способ всё равно не поможет от str = "Text" + '0';
Да... Увы, но потихоньку начинаем забывать, что С++ хоть и с двумя плюсами, но все равно С, и все, что свойственно С в нем остается.
Попробуйте тот же код на C компиляторе (без всяких ++) - и увидите, что будет и Warning (а то и error в зависимости от конкретного экземпляра компилятора. Разбираем? OK!
char c='0';
/*
Ну, тут собственно и писать не о чем... Инструкция полностью эквивалентна
BYTE c=0x30; // Илиже 84 десятичное
*/
string str="Text"+c;
/*
А тут уже интереснее "Text" вполне себе константная строка (которая кстати говоря заканчивается двоичным нулем).
Тип string - хм... Это 100% char*. Да, я понимаю, очень хотелось, чтоб это был класс, но увы, при таком объявлении - это именно тип и здается мне, что компилятор его понял именно как char* (кстати, компилятор прав).
Итак получается, что компилятор видит полны эквивалент кода
char *str = (char *)("Text") + 0x30;
Результат - ровно тот, который Вы описали... Т.е. указатель на байт по адресу (Начало строки "Text" + 48)
*/
Более чем уверен, что Вы до этого додумались и сами, но комменты насторожили... Решил таки расписать... Прошу прощения если кого-нить ненароком обидел. Видит бог это не со зла...
Все даже еще проще, в данном случае слева от знака равно стоит один тип, а справа два других типа.
Поэтому компилятор естественно производит вычисление правой части без оглядки на тип результата, и мы имеем то, что имеем.
Когда мы одно из слагаемых приводим к нужному типу - то тип результата получается ожидаемый.
Так тоже работает:
string str = "Text" + string(1, c);
Хотя конструирование строки из char - менее доходчиво.
Да нужно пользоваться всегда += с единственной переменной справа и никаких проблем не будет!
Используйте стримы или boost::lexical_cast, что в принципе одно и то же :) Суммирование строк - дурная затея. А суммирование строк и char - это вообще некрасивей некуда.
2Сергей Кищенко:
Используйте стримы или boost::lexical_cast, что в принципе одно и то же
Это, возможно, неплохой совет если речь идет о работе на PC и не об играх.
В играх обычно не используется boost, исключения еще не используются, потому что считается, что это тормозно очень. Нет, ну возможно где-то они и используются, но общие настроения такие. Мы же сейчас не на PC работаем, у нас тут скорости не те, да и работа с памятью довольно специфическая.
Пакаль (делфи) рулит =)
2Алёна: А string в gamedev не считается чем-то тяжелым? и вобще STL. :)
как на счет strncat("Text",'0',1) ?
в итоге к "Text" будет присоединен '0', добавлен '\0' в конце и возвращен указатель на начало строки.
Mario.
2Street:
Пакаль (делфи) рулит =)
Интересное замечание, особенно в сочетании с предыдущим комментарием. :-)
Мне вообще не известны консоли, к которым есть компилятор Паскаля. С/C++, ну ассемблер свой еще. Вот и всё, пожалуй.
2Андрей Валяев
2Алёна: А string в gamedev не считается чем-то тяжелым? и вобще STL. :)
Хех, я разглядела иронию :-). И тем не менее - считается. Но без STL живется совсем хреново. Поэтому он либо таки используется, но очень аккуратно, с пониманием того, как он устроен внутри. Иначе начнет вектор память переаллоцировать в самые неудачные моменты времени или еще чего...
Если у компании есть время и деньги разрабатывается свой STL. Например я когда-то писала про EASTL. Вот тот string, который участвует в примере этого поста, это не STL'евский string на самом деле. Правда тут это не важно, потому что в такой ситуации они ведут себя одинаково.
2Анонимный:
как на счет strncat("Text",'0',1) ?
в итоге к "Text" будет присоединен '0', добавлен '\0' в конце и возвращен указатель на начало строки.
Mario.
Да тут много возможных вариантов разруливания ситуации, с явным вызовом конструктора string уже был вариант.
Извините за нескромный вопрос. А зачем такой "загадочный" код может понадобиться?
2Анонимный:
Извините за нескромный вопрос. А зачем такой "загадочный" код может понадобиться?
В упрощенном виде ситуация такая: считывание массива значений из текстового конфига.
На мой взгляд с таким кодом лучше бороться "архитектурным" способом а не программным :) Т.е. никаких текстовых конфигов и парсинга в игровом коде. А во время сборки игры использовать потоки или что-нибудь еще высокоуровневое и потенциально устойчивое к ошибкам.
Предложу свой кривой вариант
string s( "Text_" );
*s.rbegin() = '1';
2Алёна:
да уж случай со строкой нечастый.
Boost конечно лучше применять с умом. Некоторые его вещи вполне подойдут для игр boost::shared_ptr, boost::any. А boost::lexical_cast полный тормоз. лучше snprintf пока-еще ничего нет, и быстро и переносимо. Со своим Wraper'ом уходит и последний минус snprintf - отсутствие проверки типов().
STL просто незаменим но тут спорный вопрос вот в Crysis используется STL Port а в Unreal свои контейнеры. Я видел коммерческий проект где написаны свои контейнеры, все настолько убого, тормознуто и с кучей ошибок, что без слез смотреть нельзя, хотя автор кода человек с законченными проектами и с большим опытом. К примеру в своей строке утечка памяти. В Сортировка пузырьком и в Critical Time коде и везде. "STL там не применяется по историческим причинам"
= считывание массива значений из текстового конфигаига =
Не сильно в теме, но - а как же считывают значения из текстового конфига в линуксе? Неужели нет готовых решений?
за-то можно так извратиться
char *s = "qweqweqwe";
int a=1;
cout << a[s] ;
Яркий пример точек следования:
решение всегда проще
char c='0';
std::string str="Text";
str =str +c;
std::cout<< str;
> Вот тот string, который участвует в примере этого поста, это не STL'евский string на самом деле. Правда тут это не важно, потому что в такой ситуации они ведут себя одинаково.
Так в этой ситуации же неважно, одинаково ведут или нет, и вообще string не причём - происходит-то всё в правой части выражения, и никак не зависит от левой. Алёна, Вы же наверняка это прекрасно понимаете, зачем же других путать? Теперь некоторые могут подумать - фу, какая гадость этот ваш string, а он вроде и не виноват ни разу :)
Мне вообще не известны консоли, к которым есть компилятор Паскаля.вообще gcc есть под некоторые консоли. под wii и ds — 100%. поэтому при большом желании можно и на паскале, и на фортране, и наверное даже на аде :)
2n2s:
Так в этой ситуации же неважно, одинаково ведут или нет, и вообще string не причём - происходит-то всё в правой части выражения, и никак не зависит от левой. Алёна, Вы же наверняка это прекрасно понимаете, зачем же других путать?
Тут важно, что конструктор string'а все так же принимает char*. Ну и то, что любые варианты решения, которые работают с STL'евским string'ом будут работать и тут.
Алёна,
Не понимаю, "всё так же принимает char*" - это в каком смысле?
Выражение "Text"+c имеет тип const char*, так же как и сам "Text". Поэтому если str="Text" работает, то и поведение str="Text"+c будет однозначным. Разве нет?
Что-то переливаем из пустого в порожнее.
Поведение char *, который прочно ассоциируется со строкой, при сложении для программиста C++ кажется неестественным.
Програмисты си легко видят здесь ошибку, потому что в си нет operator +. А программист C++ начинает сразу задумываться, что куда к чему приводится для сложения, и какое сложение здесь может быть использовано. :)
Поэтому лучше использовать string сразу, чтобы меньше думать. :)
2Сергей Кищенко:
boost-вские библиотеки очень хороши для прототипирования, но потом надо используемые классы/функции переписывать - проверено на опыте. lexical_cast очень тормозной. boost::any - удобный класс, но я после замены его на union получил почти 25% повышение производительности...
2 n2s:
Поэтому если str="Text" работает, то и поведение str="Text"+c будет однозначным. Разве нет?
Ну мало ли какой у нас string и чем он отличается от стандартного. Может, он имеет ограничение на длину в 5 символов или какие-нибудь другие экзотические ограничения.
2zg:
вообще gcc есть под некоторые консоли. под wii и ds — 100%.
Искала - не нашла. Можешь дать ссылку?
Многие пишут что Сишник бы такое не написал :) или те кто раньше писал и пишет на ассемблере + си.... ибо для них как и для меня собственно оно выглядит иначе.... что такое строка-константа "Text" - это число обычно число ... что такое '0' - тоже число ... и не более обычные числа... их сложить можно ... при этом будет новое число... но дальше мы уже работаем с этим числом как с адресом на строку ... и конечно же получается что адрес на строку указывает вовсе не туда куда якобы хотелось :) Си++ развращает программистов ИМХО
Между прочим, даже g++ -Wall не выдает предупреждений, ибо формально это совершенно правильный код: справа работает арифметика указателей...
2Алена: gcc работает везде! :)
Может эта ссылка окажется полезной?
2saabeilin: С точки зрения компилятора код правильный. но с точки зрения логики - нет. И компилятор мог бы предупретить по хорошему.
Арифметика указателей логична при использовании переменной. Но когда строка задается явно - любая арифметика ошибочна скорее всего, потому что в результате вычислений указатель на оригинальную строку пропадает. И какой тогда смысл хранить ее в исполняемом модуле как есть?
gcc например предупреждает, когда printf вызывается с некорректным количеством аргументов или с несоответствующими их типами. Хотя с точки зрения языка на три точки можно передать что угодно!
Здесь почти та же ситуация.
2 Андрей Валяев: согласен, почему и был весьма удивлен отсутствием предупреждений (gcc 4.1.2, но подозревая что и 4.3 поведет себя аналогично, нет под рукой)
Искала - не нашла. Можешь дать ссылку?да как бы и не секрет: devkitpro.org
это уже собранное с либами и т.д.
а так в gcc таргеты powerpc и arm присутствуют официально: http://gcc.gnu.org/install/specific.html
>> А string в gamedev не считается чем-то тяжелым? и вобще STL. :)
>Хех, я разглядела иронию :-). И тем не менее - считается. Но без STL живется совсем хреново.
Я вот работаю в проекте, в котором нельзя ни STL, ни исключений. Есть, правда, замена такая, в виде набора контейнеров собственного авторства. Строки есть юникодовые, векторы, мапы. Не STL, конечно, но жить можно.
А классы строк, вон вообще все кому не лень пишут. Бери любой да пользуйся.
Мде, прочитал комментарии на простой в общем случай. Кстати, кто еще хорошо помнит Builder C++ версии 3.5, тот который под DOS :) такие ошибки не допускает, с другой стороны подобные вещи сразу в глаза могут и не бросаться. Да и вообще, не ошибается только тот, кто ничего не делает.
А чтобы такого не было нужно использовать managed код :). При работе со строго типитизированными классами таких проблем не будет :)
Кстати, а если выставить повыше уровень предупреждений Warning - неужели компилятор не ругнется? (правда в этом случае он будет ругаться на все библиотеки Microsoft, но это легко обходится)
2Clevelus:
Кстати, а если выставить повыше уровень предупреждений Warning - неужели компилятор не ругнется?
У меня сейчас под рукой только VC++7.1. Проверила в нем - не ругается.
2zg:
да как бы и не секрет: devkitpro.org
это уже собранное с либами и т.д.
а так в gcc таргеты powerpc и arm присутствуют официально: http://gcc.gnu.org/install/specific.html
угу, спасибо
Хе-хе. Прикольно.
std::string str = "Text" + c;
В чём тут ошибка? Просто в понимании того, что определяет тип операции.
1) компилятор анализирует типы и обнаруживает
string = const char* + char
2) что получаем теперь при вычислении?
В первую очередь у нас будет вызван operator+ для встроенного типа const char*...
И только потом он вызовет копирующий конструктор и создаст объект типа std::string.
Это не ошибка программиста, развращённого C++, нет. Это особенность ООП-подхода конкретно в языке: вызываемый оператор определяется не возвращаемым типом, а левым операндом. И всего-то :)
Варианты:
#include <iostream>
int main ()
{
{ //исходный вариант
char c = '0';
std::string str = "Text" + c;
std::cout << str << std::endl;
}
{ //предпочитаю так
char c = '0';
std::string str = "Text";
str += c;
std::cout << str << std::endl;
}
{ //но можно и так
char c = '0';
std::string str = std::string("Text") + c;
std::cout << str << std::endl;
}
return 0;
}
Забавно!
недавно был очень похожий случай.
есть функция:
string addsign(char sign, const string & s)
{
1) return sign+s; // так нельзя
2) return string(s1)+s; //тоже нельзя
3) return string(s1, 1)+s;// только так можно... но блин не сразу допетриваешь
}
Решил это сам проверить, на компиляторе gcc 3.4.2.
вот такая программулинка:
#include <string>
#include <stdio.h>
using namespace std;
string addsign(char s1, const string & s)
{
//1)return s1+s; // так нельзя
//2)return string(s1)+s; //тоже нельзя
//3)return string(s1, 1)+s;// только так можно... но блин не сразу допетриваешь
//4)return string(&s1, 1)+s;
}
int main(int n, char **args)
{
string text = "1.9876";
string res = addsign('-', text);
puts(res.c_str());
return 0;
}
выдала мне срвсем другое:
первый вариант (как "нельзя") выдал правильный результат: -1.9876, второй вариант - ошибку компиляции, а третий(который "только так и можно") дал нечто невразумительное - кучу смеющихся рожиц и в конце-число 1.9876. Мне даже непонятно, как у автора сообщения получился нормальный результат.
Последний (4-ый, "мой" вариант) дал тоже верный результат: -1.9876
Малость ошибка закралась в Ваши рассуждения :)
string addsign(char s1, const string & s)
{
//1)return s1+s; // так нельзя
тут то как раз все можно, в STL есть оператор +, который в качестве одного из операндов принимает char, второго - string
//2)return string(s1)+s; //тоже нельзя
ага, компилятор ругается, нет такого конструктора у string
//3)return string(s1, 1)+s;// только так можно... но блин не сразу допетриваешь
все замечательно, только параметры у конструктора перепутали, в итоге создается строка из s1 символов с кодом 1 (ага, тех самых, смеющися рожиц :) )
//4)return string(&s1, 1)+s;
}
тут тоже все хорошо - string создается по одному символу из строки &s1
проверялось на VC8(SP1)
вас stl'щиков в утиль давно пора.
если лень писать свои классы, чтобы были шустрые и расчитанные на мультипроцессор, пишите на C#.
микрософт так старались appdev от c++ отделить, а вы все ещё кипятите.
оставьте c++ для системных прогеров :)
да btw, советую почитать ISO стандарт c++.
по стандарту код
char c='0';
string str="Text"+c;
это
class char c = __int32(0x30)
class string str = __int32(char*("Text")) + __int32(c);
т.е все приводится к классу int перед выполением стандартных операторов, незнать это тяжкий грех!
например
longlong n;
long t = 0;
n = t -1;
что будет в n? -1? ага три раза оно там будет. там будет 0xffffffff
что для longlong не -1.
на х86 это нормально, а вот х64... таких программистов надо растреливать.
char c='0';
string str = string("Text")+c;
приведение типов спасет этот мир.
Помоему Алёне давно пора закрывать эту дискуссию, а комментирующим не мешает читать впередиидущие комментарии.
2Андрей Валяев:
Помоему Алёне давно пора закрывать эту дискуссию, а комментирующим не мешает читать впередиидущие комментарии.
Да уж, а то разговор пошел по кругу.
И 53 комментария на две строки кода - это как-то многовато.
Давайте завершим на этом.
Тут упоминали php, а в нём кокатенация строк происходит через оператор "точка".
$a= "10Text".1; // $a= "10Text1";
$b= "10Text"+1; // $b= 10+1;
Через + происходит сложение чисел.
Проблема ясна. Символ должен быть символом, а байт - байтом )
Андрей Валяев пишет...
string str = string("Text") + c;
Создаем временную переменную (вызываем конструктор), вызываем перегруженный operator+, вызываем перегруженный operator=, удаляем временный объект (вызываем деструктор)...
Mario пишет...
как на счет strncat("Text",'0',1) ?
в итоге к "Text" будет присоединен '0', добавлен '\0' в конце и возвращен указатель на начало строки.
Во-первых, не strncat("Text",'0',1), а strncat("Text","0",1)...
Во-вторых, запись в const-секцию (которая read-only)? Тогда вызови для начала VirtualProtect... И убедись, что после "Text" не лежит "Text2".
coff
Еще раз убеждаюсь что нужно начинать учить с основ. Вы будете удивлены сколько времени еще сэкономите, я обещаю.
Например я подобный вопрос на собеседовании задаю, в качестве отсева по телефону.
Отправить комментарий