При портировании кода на линукс столкнулся с проблемой
От: gwg-605 Россия  
Дата: 09.08.19 10:46
Оценка: -1 :)))
Портирую код с винды на линукс/мак.

template<typename TYPE>
class ttt {
public:
    ttt() {
        m_Pointer = "<null>" ;
    }

    ttt( const ttt<TYPE>& src ) {
        m_Pointer = src.m_Pointer ;
    }

    const TYPE* m_Pointer ;
};
void main() {
    ttt<char> c0 ;
    c0.m_Pointer = "Hello World!!!" ;
    printf( "%u--%s--%u", 0x55, c0, 0xAA ) ;
}

GCC передает не копию лежащего в c0, а указатель на c0, и получается полный алес! Если же убрать копи конструктор из декларации класса, то замечательно передается копия и все работает. Причем GCC сам генерит копи конструктор, который и вызывает. При моем копи конструкторе даже упоминания нет.

Вторая проблема с clang, он компилирует код с -Wno-error=non-pod-varargs, но вставляет ud2 (invalid opcode) перед сгенерированным кодом, те мы сделали что ты просил, но фиг ты это запустишь. Это можно как-то обойти?

ЗЫ. Все эксперименты проводились при выключенных оптимизациях.
ЗЫЫ. здесь можно поэксперементировать: https://godbolt.org/z/-PMwXh
Re: При портировании кода на линукс столкнулся с проблемой
От: watchmaker  
Дата: 09.08.19 10:55
Оценка: +6
Здравствуйте, gwg-605, Вы писали:


G6>GCC передает не копию лежащего в c0, а указатель на c0, и получается полный алес!


G6>Вторая проблема с clang, . Это можно как-то обойти?


Обе проблемы решаются исправлением кода в соответствии со стандартом языка:
printf( "%u--%s--%u", 0x55, c0.m_Pointer, 0xAA);


G6>Портирую код с винды на линукс/мак.

Ну к отличиям винды от других ОС тут проблема не имеет отношения.
Re: При портировании кода на линукс столкнулся с проблемой
От: Vamp Россия  
Дата: 09.08.19 11:20
Оценка:
Неопределенное поведение. Чего ты от компилятора хочешь?
Да здравствует мыло душистое и веревка пушистая.
Re[2]: При портировании кода на линукс столкнулся с проблемой
От: gwg-605 Россия  
Дата: 09.08.19 11:56
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Обе проблемы решаются исправлением кода в соответствии со стандартом языка:
printf( "%u--%s--%u", 0x55, c0.m_Pointer, 0xAA);

Можно ссылочку в стандарте на эту часть?
Re[3]: При портировании кода на линукс столкнулся с проблемо
От: watchmaker  
Дата: 09.08.19 12:08
Оценка:
Здравствуйте, gwg-605, Вы писали:

G6>Здравствуйте, watchmaker, Вы писали:


W>>Обе проблемы решаются исправлением кода в соответствии со стандартом языка:
printf( "%u--%s--%u", 0x55, c0.m_Pointer, 0xAA);

G6>Можно ссылочку в стандарте на эту часть?

Важны эти пункты:

[expr.call]
When there is no parameter for a given argument, the argument is passed in such a way that the receiving
function can obtain the value of the argument by invoking va_arg
...
Passing a potentially-evaluated argument of class type (Clause 12) having a non-trivial copy constructor, a non-trivial move constructor, or a non-trivial destructor, with no corresponding parameter, is conditionally-supported with implementation-defined semantics.

То есть нет гарантий работоспособности передачи такого типа через .... То есть это заведомо непереносимо, т.к. "implementation-defined semantics" может быть UB или отказом от компиляции.

Но если если "implementation-defined semantics" устраивает для известного множества версий компиляторов (например, как в gcc), то формально писать так можно.
clang тут прав.
gcc, с разным способом передачи в зависимости от наличия конструктора, тоже прав.

А вот чтение параметра не того типа — уже UB, кроме случаев

— one type is a signed integer type, the other type is the corresponding unsigned integer type, and the value is representable in both types;
— one type is pointer to void and the other is a pointer to a character type.

printf будет читать в соответствии с спецификатором %s, а нужно только с va_arg(…, ttt<char>) (ну и при условии предыдущего пункта).
Отредактировано 09.08.2019 14:07 watchmaker . Предыдущая версия . Еще …
Отредактировано 09.08.2019 12:10 watchmaker . Предыдущая версия .
Re[2]: При портировании кода на линукс столкнулся с проблемой
От: gwg-605 Россия  
Дата: 09.08.19 12:13
Оценка:
Здравствуйте, Vamp, Вы писали:

V>Неопределенное поведение. Чего ты от компилятора хочешь?

1. В чем оно неопределенное? Кладу копию объекта в качестве параметра. Насколько я понимаю это дефолтное поведение для функций с переменным числом параметров.
Какое поведение должно быть у такого кода:
int myfunc( int myformat, ... ) ;

main() {
   struct S {
     int m1;
     int m2 ;
   };

   S mys{1,1}
   
   myfunc( 1, mys ) ;
}

при наличии у S копи конструктора и при отсутвии? Как мне угадывать в myfunc, что компилятор мне передал: копию объекта или поинтер на него?

2. Почему наличие явного копи-конструктора меняет поведение компилятора?
Re[3]: При портировании кода на линукс столкнулся с проблемой
От: watchmaker  
Дата: 09.08.19 12:20
Оценка:
Здравствуйте, gwg-605, Вы писали:


G6>Как мне угадывать в myfunc, что компилятор мне передал: копию объекта или поинтер на него?

Угадывать не надо. Надо использовать va_arg с правильным типом (S, в данном случае). Компилятор сам возьмёт объект откуда нужно.

G6>2. Почему наличие явного копи-конструктора меняет поведение компилятора?

Для типов с нетривиальными конструкторами другие правила.
Re[4]: При портировании кода на линукс столкнулся с проблемо
От: sergii.p  
Дата: 09.08.19 12:22
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>gcc, с разным способом передачи в зависимости от наличия конструктора, тоже прав.


W>А вот чтение параметра не того типа — уже всегда UB:

W>

W>[cstdarg.syn]
W>If the parameter parmN is of a reference type, or of a type that is not compatible with the type that results when passing an argument for which there is no parameter, the behavior is undefined.

W>printf будет читать в соответствии с спецификатором %s, а нужно только с va_arg(…, ttt<char>) (ну и при условии предыдущего пункта).

то есть получается gcc целенаправленно загнал себя в UB, передав ссылку и наперёд зная, что там va_arg? Как же тут говорить о правоте компилятора?
Re[5]: При портировании кода на линукс столкнулся с проблемо
От: watchmaker  
Дата: 09.08.19 12:28
Оценка:
Здравствуйте, sergii.p, Вы писали:


SP> передав ссылку и наперёд зная, что там va_arg?

И?
Это не запрещено и может быть использовано правильно (с gcc): https://godbolt.org/z/0aolWi — никаких проблем. Если передаётся по ссылке, то и читается по ссылке, а если передаётся по значению (после удаления конструкторов), то и читается по значению.

SP>то есть получается gcc целенаправленно загнал себя в UB, передав ссылку и наперёд зная, что там va_arg? Как же тут говорить о правоте компилятора?

Нет. У gcc всё хорошо. Проблема в исходном коде автора темы.
Тут ситуация аналогична, например, коду printf("%s", 42);. В чём тут винить компилятор?
Отредактировано 09.08.2019 12:42 watchmaker . Предыдущая версия .
Re: При портировании кода на линукс столкнулся с проблемой
От: Zhendos  
Дата: 09.08.19 13:07
Оценка: 4 (1) +1
Здравствуйте, gwg-605, Вы писали:

G6>Портирую код с винды на линукс/мак.


G6>
G6>template<typename TYPE>
G6>class ttt {
G6>public:
G6>    ttt() {
G6>        m_Pointer = "<null>" ;
G6>    }

G6>    ttt( const ttt<TYPE>& src ) {
G6>        m_Pointer = src.m_Pointer ;
G6>    }

G6>    const TYPE* m_Pointer ;
G6>};
G6>void main() {
G6>    ttt<char> c0 ;
G6>    c0.m_Pointer = "Hello World!!!" ;
G6>    printf( "%u--%s--%u", 0x55, c0, 0xAA ) ;
G6>}
G6>

G6>GCC передает не копию лежащего в c0, а указатель на c0, и получается полный алес!

У gcc есть отличный ключ -Werror=format , с его помощью вы сможете весь свой проект
избавить от этого ужасного кода, очень рекомендую. clang по идее тоже поддерживает этот ключ компиляции.
Отредактировано 10.08.2019 10:59 Zhendos . Предыдущая версия .
Re[2]: При портировании кода на линукс столкнулся с проблемой
От: σ  
Дата: 09.08.19 13:50
Оценка:
W>Обе проблемы решаются исправлением кода в соответствии со стандартом языка:
printf( "%u--%s--%u", 0x55, c0.m_Pointer, 0xAA);

Всё ещё не соответствует стандарту языка (содержит UB).
Re[3]: При портировании кода на линукс столкнулся с проблемой
От: AleksandrN Россия  
Дата: 10.08.19 21:18
Оценка:
Здравствуйте, gwg-605, Вы писали:

G6>Здравствуйте, Vamp, Вы писали:


V>>Неопределенное поведение. Чего ты от компилятора хочешь?

G6>1. В чем оно неопределенное? Кладу копию объекта в качестве параметра.

Когда printf увидит %s, то будет считать, что параметр — char*. А если там что-то другое, то что произойдёт, если переданные данные будут интерпретированы как указатель и будет обращение по этому адресу?

Компилируй с параметром -Wall, тогда компилятор будет выдавать предупреждение, если передаваемые в printf параметры не соответствуют формату. А лучше, как уже советовали -Werror=format, тогда вообще не соберёшь в этом случае.
Re[2]: При портировании кода на линукс столкнулся с проблемой
От: AleksandrN Россия  
Дата: 10.08.19 22:34
Оценка:
Здравствуйте, Zhendos, Вы писали:

Z>clang по идее тоже поддерживает этот ключ компиляции.


Поддерживает. При сборке с параметрами по умолчанию, clang выдаёт предупреждение при несоответствии формата и аргументов printf. А данный пример вообще не компилирует

1.cpp:19:33: error: cannot pass non-POD object of type 'ttt<char>' to variadic function; expected type from format string was 'char *' [-Wnon-pod-varargs]
    printf( "%u--%s--%u", 0x55, c0, 0xAA ) ;
                 ~~             ^~

Номер строки указан с учётом #include <stdio.h> и пустой строки в начале файла.
Re: При портировании кода на линукс столкнулся с проблемой
От: gwg-605 Россия  
Дата: 15.08.19 12:40
Оценка: +1 :))) :))
Прошу прощения за офтопик, но некоторое подведение итогов обсуждения.

1. Спасибо всем кто учавствовал в обсуждении, согласен использовал implementation depended вещи которые могут приводить к UB, что и получил.
2. Задача которую хотел сделать оказалась невозможна те избавиться от "мусора вида .cstr()" в printf( "%s", mystring.cstr() ) ; те в mystring лежит только один поинтер и он указывает на строку. И очень жаль, что "супер умные" компиляторы могут сделать соответствие формата с параметрами, а понять, что в классе лежит строка они не могут ((
3. Развитие С++ с каждым годом удручает, раньше ты хотел выстрелить себе в ногу, ты брал и стрелял, а сейчас тебе надо изголиться чтобы попытаться выстрелить себе в ногу, но некоторые компиляторы вообще убъют тебя раньше чем ты попытаешься выстрелить себе в ногу.
Отредактировано 15.08.2019 12:42 gwg-605 . Предыдущая версия .
Re[2]: При портировании кода на линукс столкнулся с проблемой
От: reversecode  
Дата: 15.08.19 12:48
Оценка: +2
Здравствуйте, gwg-605, Вы писали:

G6>Прошу прощения за офтопик, но некоторое подведение итогов обсуждения.

[...]
G6>2. Задача которую хотел сделать оказалась невозможна те избавиться от "мусора вида .cstr()" в printf( "%s", mystring.cstr() ) ; те в mystring лежит только один поинтер и он указывает на строку. И очень жаль, что "супер умные" компиляторы могут сделать соответствие формата с параметрами, а понять, что в классе лежит строка они не могут ((

в языке C++ нет printf

G6>3. Развитие С++ с каждым годом удручает, раньше ты хотел выстрелить себе в ногу, ты брал и стрелял, а сейчас тебе надо изголиться чтобы попытаться выстрелить себе в ногу, но некоторые компиляторы вообще убъют тебя раньше чем ты попытаешься выстрелить себе в ногу.


C++ следует стандарту
очень жаль что с каждым годом все больше людей хотят что бы С++ делал что то так как им хочется
а не так как написано в стандарте
Re[2]: При портировании кода на линукс столкнулся с проблемой
От: AleksandrN Россия  
Дата: 15.08.19 22:50
Оценка: +1
Здравствуйте, gwg-605, Вы писали:

G6>И очень жаль, что "супер умные" компиляторы могут сделать соответствие формата с параметрами, а понять, что в классе лежит строка они не могут ((


Понять, что лежит в классе, компилятор может. Но телепатия в компиляторе стандартом не предусмотрена, а без неё компилятору непонятно, что ты её хочешь в printf засунуть, если ты явно компилятору это не скажешь, что надо char* передать.

G6>3. Развитие С++ с каждым годом удручает, раньше ты хотел выстрелить себе в ногу, ты брал и стрелял, а сейчас тебе надо изголиться чтобы попытаться выстрелить себе в ногу, но некоторые компиляторы вообще убъют тебя раньше чем ты попытаешься выстрелить себе в ногу.


Ты и сейчас можешь в ногу себе выстрелить. Арсенал для этого большой — от водяного пистолета до РСЗО. И ты выстрелил в ногу классическим способом — передал в printf для %s не char*, а данные другого типа. Этот способ стрельбы по ногам используется со времён первых версий Си.
Re[2]: При портировании кода на линукс столкнулся с проблемой
От: Mr.Delphist  
Дата: 20.08.19 12:59
Оценка: +1
Здравствуйте, gwg-605, Вы писали:

G6>2. Задача которую хотел сделать оказалась невозможна те избавиться от "мусора вида .cstr()" в printf( "%s", mystring.cstr() ) ;


Почему мусор? Нао лишь понимать, что mystring ДОЛЖЕН делать, а что он делать НЕ ОБЯЗАН (хотя иногда и может для собственного удобства).

Это особенно важно если смешивать стилистику C и C++, потому что одна часть код предявляет набор контрактов-требований, которые не выполняются (или выполняются частично), а сам по себе ANSI C слишком прост чтобы догадаться о предполагаемых неявностях.
Re[3]: При портировании кода на линукс столкнулся с проблемой
От: B0FEE664  
Дата: 03.09.19 12:19
Оценка:
Здравствуйте, σ, Вы писали:

W>>Обе проблемы решаются исправлением кода в соответствии со стандартом языка:
printf( "%u--%s--%u", 0x55, c0.m_Pointer, 0xAA);

σ>Всё ещё не соответствует стандарту языка (содержит UB).

В чём тут проблема?
И каждый день — без права на ошибку...
Re[2]: При портировании кода на линукс столкнулся с проблемой
От: Molchalnik  
Дата: 08.11.19 21:54
Оценка:
Здравствуйте, gwg-605, Вы писали:

G6>Прошу прощения за офтопик, но некоторое подведение итогов обсуждения.


G6>2. Задача которую хотел сделать оказалась невозможна те избавиться от "мусора вида .cstr()" в printf( "%s", mystring.cstr() ) ; те в mystring лежит только один поинтер и он указывает на строку. И очень жаль, что "супер умные" компиляторы могут сделать соответствие формата с параметрами, а понять, что в классе лежит строка они не могут ((


Почему же? вполне возможна. Вопрос лишь, насколько ты хороший танцор.

G6>3. Развитие С++ с каждым годом удручает, раньше ты хотел выстрелить себе в ногу, ты брал и стрелял, а сейчас тебе надо изголиться чтобы попытаться выстрелить себе в ногу, но некоторые компиляторы вообще убъют тебя раньше чем ты попытаешься выстрелить себе в ногу.


Да нет же, С++ прекрасен, только легаси его портит, если догадаются ввести тэг version 23 {}, жизнь наладится

По твоей задаче: если ты достаточно хороший танцор, то можно сделать так:

#include <string>
#include <cstdio>

template <typename ParamType> struct Has_c_str_Method {
  static constexpr const bool value = false;
};

template<> struct Has_c_str_Method< std::string > {
  static constexpr const bool value = true;
};

template <typename ParamType> decltype(auto) GetCString( const ParamType & value ) {
  if constexpr ( Has_c_str_Method< ParamType >::value ) {
    return value.c_str();
  } else {
    return value;
  }
}

template <typename... Args> int SmartPrintf( const char * format, Args&&... args ) {
    return printf(format, GetCString( std::forward<Args>(args) ) ... );
}

int main() {
    std::string str{"C++string++"};
    const char * cstr{"MyCString"};
    int a = 4;
    SmartPrintf("\n%s %s %u", str, cstr, a );
    return 0;    
}

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