Вопрос об аргументах printf. С/С++

У нас есть следующий фрагмент кода:

char tab[2][3] = {'1', '2', '\0', '3', '4', '\0'};
printf("%s\n", tab);

И я не понимаю, почему мы не получаем ошибку/предупреждение при вызове printf. Я получаю предупреждение, но не ошибку, и программа работает нормально. Он печатает '12'.
printf ожидает аргумент типа char *, то есть указатель на char. Итак, если бы я объявил char arr[3], то arr — это адрес блока памяти, который содержит char, поэтому, если бы я вызвал printf с ним, он распался бы на указатель на char, то есть char *.
Аналогично, tab — это адрес блока памяти, который содержит тип массив из 3 символов, который, в свою очередь, адрес блока памяти содержит char, поэтому tab будет распадаться на char **, и это должно быть проблема, так как printf ожидает char *.

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

Приложение:

Я получаю следующее предупреждение:
a.c:6: warning: char format, different type arg (arg 2)


person Ori Popowski    schedule 29.06.2009    source источник
comment
Ниже вы говорите, что это работает, а здесь утверждаете, что получаете ошибку/предупреждение. Что именно вы получаете? Вполне логично, что вы получаете предупреждение с этим кодом, подверженным ошибкам, но вы получаете ошибку?   -  person Daniel Daranas    schedule 29.06.2009


Ответы (4)


Пример источника

#include <stdio.h>

int main( void ) {
  char tab[2][3] = {'1', '2', '\0', '3', '4', '\0'};
  printf("%s\n", tab);

  return 0;
}

Предупреждение о компиляции

$ gcc test.c
test.c: In function ‘main’:
test.c:5: warning: format ‘%s’ expects type ‘char *’, but argument 2 has type ‘char (*)[3]’

Указатели есть указатели

Аргумент %s для printf указывает функции, что она будет получать указатель (на строку). Строка в C — это просто последовательность байтов, оканчивающаяся символом ASCII-Z. Переменная tab[2][3] является указателем. Некоторые компиляторы выдают предупреждение о несоответствии указателя. Тем не менее, код все равно должен выводить 12, потому что код printf перемещается по памяти, начиная с указателя, который ему был передан (печать символов по ходу), пока не найдет нулевой байт. 1, 2 и \0 устанавливаются в памяти последовательно, начиная с адреса, представленного переменной tab.

Эксперимент

В качестве эксперимента, что произойдет, если вы скомпилируете и запустите следующий код:

#include <stdio.h>

int main( void ) {
  char tab[2][3] = {'1', '2', '\0', '3', '4', '\0'};
  printf("%s\n", tab[1]);

  return 0;
}

Не бойтесь экспериментировать. Посмотрите, сможете ли вы найти ответ на основе того, что вы теперь знаете. Как бы вы сослались на tab сейчас (в свете эксперимента), чтобы избавиться от предупреждения и по-прежнему отображать 12?

person Dave Jarvis    schedule 29.06.2009
comment
Это просто. Это правильный способ вызова printf. tab[1] имеет тип char[], и это то же самое, что и вызов printf с char *. Так что все оптимально. - person Ori Popowski; 29.06.2009
comment
В яблочко. Таким образом, чтобы отобразить 12, вы должны использовать tab[0]. - person Dave Jarvis; 29.06.2009

Параметр табуляции соответствует многоточию в вызове printf(). Компиляторы C и C++ не обязаны проверять такие параметры.

person Community    schedule 29.06.2009

Ваше предположение, что tab будет распадаться на char **, неверно: tab имеет тип char [2][3], т.е. оно распадается на char (*) [3]. Важно понимать, что хотя массивы и указатели часто ведут себя одинаково, это не одно и то же. printf() ожидает char *, поэтому он берет биты char (*) [3] и соответственно интерпретирует их. Хотя это работает на вашей платформе, стандарт C не гарантирует этого: оба указателя ссылаются на одну и ту же ячейку памяти, но их представление не обязательно должно быть идентичным.

Проверьте мой ответ на этот связанный вопрос для получения подробной информации.

person Christoph    schedule 29.06.2009
comment
+1, я знал, что где-то уже был ответ, но не нашел вопроса. Единственное похожее, что я нашел, это следующее: ком/вопросы/232303/ - person quinmars; 29.06.2009

Вы, кажется, сами это объяснили, я не вижу, что осталось сказать.

tab представляет собой массив из двух char *. Каждый элемент tab представляет собой строку, которую может принять printf, но сам tab неприемлем, так как это указатель на указатель на char.

person sykora    schedule 29.06.2009
comment
tab и tab[0] указывают на одно и то же место (строку 12), поэтому они оба работают, несмотря на несоответствие. - person Carson Myers; 29.06.2009
comment
Ваша проблема, похоже, в том, что компилятор принимает код. См. ответ Нила Баттерворта, почему это так. Честно говоря, я удивлен, что компилятор знает о семантике printf() и выполняет какую-либо проверку типов своих аргументов. - person Ari; 01.07.2009