откуда printf знает адрес символьных данных CString? - PullRequest
7 голосов
/ 04 февраля 2011

Учитывая этот фрагмент кода:

struct My {
  operator const char*()const{ return "my"; }
} my;

CStringA s( "aha" );
printf("%s %s", s, my );


// another variadic function to get rid of comments about printf :)
void foo( int i, ... ) {
  va_list vars;
  va_start(vars, i);
  for( const char* p = va_arg(vars,const char*)
     ; p != NULL
     ; p=va_arg(vars,const char*) ) 
  {
    std::cout << p << std::endl;
  }
  va_end(vars);
}
foo( 1, s, my );

Этот фрагмент приводит к «интуитивному» выводу «ага». Но я понятия не имею, как это может работать:

  • если вызов функции variadic переводится в нажатие указателей аргументов, printf получит CStringA*, который интерпретируется как const char*
  • если вызов функции variadic вызывает operator (const char*), почему бы не сделать это для моего собственного класса?

Может кто-нибудь объяснить это?

EDIT: добавлена ​​фиктивная переменная функция, которая обрабатывает свои аргументы как const char* s. Вот - он даже падает, когда достигает аргумента my ...

Ответы [ 7 ]

7 голосов
/ 04 февраля 2011

Соответствующий текст стандарта C ++ 98 §5.2.2 / 7:

Стандартные преобразования lvalue-to-rvalue (4.1), array-to-pointer (4.2) и function-to-pointer (4.3) выполняются в выражении аргумента. После этих преобразований, если аргумент не имеет арифметику, перечисление, указатель, указатель на член или тип класса, программа становится некорректной. Если аргумент имеет тип класса не POD (пункт 9), поведение не определено.

Так что формально поведение не определено .

Однако данный компилятор может предоставить любое количество языковых расширений, и Visual C ++ делает. Библиотека MSDN документирует поведение Visual C ++ следующим образом в отношении передачи аргументов ...:

  • Если фактический аргумент имеет тип float, ему присваивается тип double перед вызовом функции.
  • Любое знаковое или беззнаковое поле типа char, short, enumerated type или bit преобразуется либо в int со знаком, либо в unsigned, используя интегральное продвижение.
  • Любой аргумент типа класса передается по значению в виде структуры данных; копия создается двоичным копированием вместо вызова конструктора копирования класса (если он существует).

Это ничего не говорит о том, что Visual C ++ применяет определенные пользователем преобразования.

Возможно, CString::Format, функция, которая вас действительно интересует, зависит от последнего пункта выше.

Приветствия и hth.,

6 голосов
/ 04 февраля 2011

То, что вы делаете, это неопределенное поведение и является либо нестандартным расширением, предоставляемым вашим компилятором, либо работает по счастливой случайности.Я предполагаю, что CString хранит строковые данные в качестве первого элемента в структуре, и, таким образом, чтение из CString, как если бы это было char *, дает действительную строку с нулевым символом в конце.

4 голосов
/ 04 февраля 2011

Вы не можете вставлять данные, отличные от POD, в функции с переменными числами. Подробнее

1 голос
/ 04 февраля 2011

Это не так.Он даже не называет operator const char*.Visual C ++ просто передает данные класса printf, как если бы memcpy.Он работает из-за макета класса CString: он содержит только одну переменную-член, которая является указателем на символьные данные.

1 голос
/ 04 февраля 2011

Если вызов функции variadic переводится в нажатие указателей аргументов,…

Это не так, как работают функции variadic. значения аргументов, а не указатели на аргументы, передаются после специальных правил преобразования для встроенных типов (таких как char в int).

C ++ 03 §5.2.2p7:

Когда для данного аргумента нет параметра, аргумент передается таким образом, что принимающая функция может получить значение аргумента, вызвав va_arg (18.7).Стандартные преобразования lvalue-to-rvalue (4.1), массив-указатель (4.2) и функция-указатель (4.3) выполняются в выражении аргумента.После этих преобразований, если аргумент не имеет арифметику, перечисление, указатель, указатель на член или тип класса, программа становится некорректной.Если аргумент имеет тип класса не POD (раздел 9), поведение не определено.Если аргумент имеет целочисленный тип или тип перечисления, на который распространяется продвижение по типу (4.5), или тип с плавающей запятой, который подлежит продвижению с плавающей запятой (4.6), значение аргумента преобразуется в повышенный тип до вызова,Эти продвижения называются продвижениями аргументов по умолчанию.

В частности, из вышесказанного:

Если аргумент имеет тип класса не POD (пункт 9),поведение не определено.

C ++ указывает на C для определения va_arg, а C99 TC3 §7.15.1.2p2 говорит:

… если тип не совместим стип фактического следующего аргумента (как продвигается в соответствии с продвижением аргументов по умолчанию), поведение не определено, за исключением следующих случаев: [список случаев, которые здесь не применяются]

Таким образомЕсли вы передаете тип класса, это должен быть POD, а принимающая функция должна применять правильный тип, в противном случае поведение не определено.Это означает, что в худшем случае он может работать точно так, как вы ожидаете.

Printf не будет применять правильный тип для любого пользовательского типа класса, так как он не знает о них, поэтому вы не можете передать любой UDTТип класса для печати.Ваш foo делает то же самое, используя указатель на символ вместо правильного типа класса.

1 голос
/ 04 февраля 2011

если вызов функции variadic вызывает для него оператор (const char *), почему бы не сделать это для моего собственного класса?

Да, но вы должны явно привести приведениеэто в вашем коде: printf("%s", (LPCSTR)s, ...);.

0 голосов
/ 04 февраля 2011

Ваше утверждение printf неверно:

printf("%s", s, my );

Должно быть:

printf("%s %s", s, my);

Что выведет "aha my".

CString имеет оператор разговорадля const char* (фактически для LPCTSTR, то есть const TCHAR* - CStringA имеет функцию преобразования для LPCSTR).

Вызов printf не преобразует ваш объект CStringAна указатель CStringA*.По сути, это относится к void*.В случае с CString, это просто удача (или, возможно, дизайн разработчиков Microsoft, использующих преимущества чего-то, что не входит в стандарт), что он даст вам строку с завершением NULL.Если бы вместо этого вы использовали _bstr_t (который имеет размер строки первым), несмотря на наличие функции преобразования, он ужасно потерпел бы неудачу.

Это хорошая практика (и требуется во многих случаях) дляявным образом приведите ваши объекты / указатели к тому, что вы хотите, чтобы они были, когда вы вызываете printf (или любую переменную функцию в этом отношении).

...