Многие у себя в блогах публикуют разные головоломные задачки, а тут как раз сегодня у сотрудника возникла ситуация над которой мы некоторое время помедитировали, пока не пришел nimblemag и не обьяснил. Код, собственно, ниже. Код рабочий. Обьяснить почему работает.
Ответ ниже белым по белому (да, такой способ я у Эльдара Мусаева подсмотрел) чтоб посмотреть выделяем текст
===отсюда===
На самом деле все просто и вытекает из комутативности операции сложения:
#include <cstdlib>
#include <iostream>
using namespace std;
int main (int argc, char** argv){
cout<<(*argv+(argc-1))<<endl<<((argc-1)+*argv)<<endl;
cout<<*(argv+(argc-1))<<endl<<*((argc-1)+argv)<<endl;
return EXIT_SUCCESS;
}
===досюда===
Отдельное Спасибо White Knight за нахождение ошибки в коде ответа(см. комментарии).
#include <cstdlib>
#include <iostream>
using namespace std;
int main (int argc, char** argv){
cout<<argv[argc-1]<<endl
<<argc[argv-1]<<endl;
return EXIT_SUCCESS;
}
Ответ ниже белым по белому (да, такой способ я у Эльдара Мусаева подсмотрел) чтоб посмотреть выделяем текст
===отсюда===
На самом деле все просто и вытекает из комутативности операции сложения:
#include <cstdlib>
#include <iostream>
using namespace std;
int main (int argc, char** argv){
cout<<*(argv+(argc-1))<<endl<<*((argc-1)+argv)<<endl;
return EXIT_SUCCESS;
}
===досюда===
Отдельное Спасибо White Knight за нахождение ошибки в коде ответа(см. комментарии).
В принципе тривиально, если не забывать про это свойство сишных массивов.
ReplyDeleteГораздо больше времени требуется чтобы понять как это всетаки работает. То есть что хотел этим кодом изобразить автор. :)
Хотя вот откомментировал и задумался... сложение int с чем либо изменяет int известно на сколько. А вот сложение чего-то с указателем зависит от типа указателя. Хотя не, все логично. в первом случае массив указателей argv и индекс int - 1, во втором случае int преобразовывается в безтиповый указатель вероятно, размер элемента которого вроде бы приравнивается к 1 в некоторых версиях компиляторов, в то время как argv выступает в роли индекса но -1 уменьшает его на размер указателя.
И еще один момент как воспримет ostream argc[argv], если он ошибется с типом может получиться очень нехорошо.
И главное все прекрасно понимают что за такой код руки поотрывать мало, но тем не менее постоянно спрашивают это на собеседованиях... :) дескать надо знать. Тоже верно.
И главное никаких варнингов от компилятора... загадочно...
ReplyDeleteЯ понимаю что есть комутативность операции сложения и всякое такое.. мне не понятно как компилятор пропускает такое без ругани??? Тем более что это строгий в отношении типов компилятор С++.... :)
Не иначе как опять таки - зло совместимости со старыми сишными проектами, которые эту фишку активно юзали когда-то.
я думаю что тут дело не в совместимости, а именно в интерпретации массивов как указателей и свойствах операции над ними. Теоретически код сгенерированный для a[i] и a+i должен выглядеть одинаково, потому и для i+a и i[a] должен выглядеть тоже одинаково, иначе нарушается принцип коммутативности, а следовательно и компилятор матюкаться не обязан, так как такое поведение вполне обоснованно.
ReplyDeleteЭто в си все в принципе обоснованно. А в С++ все должно быть гораздо сложнее, хотя бы потому, что там есть operator []...
ReplyDeleteкстати нашел вот в gcc: gcc/gcc/c-typeck.c, функция build_array_ref.
Если компилятор видит, что в перед скобкой не указатель а в скобке указатель - то он их просто молча меняет местами.
В то время как отсутствие хотя бы одного указателя - argc[argc - 1] вызывает ошибку.
ИМХО, это продиктовано именно требованиями совместимости. То есть раньше это видимо активно практиковали. :)
Непонятно почему C++ это допускает. Я бы запретил. :)
Век живи - век учись))
ReplyDeleteДаже такой код проходит:
char str[] = "1";
int i = 0;
i[str] = '2';
cout << str;
Взглянув на такой код, ни о какой коммутативности и думать не придется. В голове будет стоять только один вопрос: "Как??.." ))
И вот попробуй скажи на собеседовании, что это нифига никакая не комутативность, а просто компилятор молча переставляет то, что глупые программисты сдуру написали наоборот...
ReplyDeleteНе поймут. :)
Сергей, а твой код вообще странно, что работает... Потому что "1" это по сути своей const char *. По идее он должен располагаться в .rodata, И ни о какой модификации не должно быть и речи.
ReplyDeleteКрайне странно, что работает.
Но даже не смотря на это за i[str] всеравно стоит отрывать руки по самое... :)
char str[] = "1"; - инициализация массива - элементы копируются, поэтому его менять можно. РО было бы, если бы код выглядел вот так:
ReplyDeletechar * str = "1";
вот, нашел.
ReplyDeleteВо-первых, по стандарту не совсем валидный - выход указателя за начало массива. array-1+2 это не array+2-1, в первом выражении ошибка, во втором нет.
ReplyDeleteВо-вторых, вроде же вменяемые интервьюверы высказывают своё "Фу" по задачам на игру в синтаксис и малоизвестные особенности.
Так как человек, который действительно хорошо знает С++ и вообще как решать проблемы и создавать программы, может либо озвереть и доказать интервьюверу что это ОН не знает С++, либо просто составить плохое мнение о компании, либо просто зависнуть от некорректности вопроса (какой конкретно из подвохов имелся ввиду, и почему невалидный код называют валидным).
This comment has been removed by the author.
ReplyDeleteЕщё один подвох - http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#343 . Тут используется endl, а где он объявлен?
ReplyDeleteYuriy Volkov, да я подозревал что копируется. был неправ.
ReplyDeleteКстати по поводу кода. Многие процессоры умеют оперировать с памятью посредством базы/индекса/множителя. И собственно приведение операции индексирования к сложению не позволит использовать весьма эффективные оптимизации подобного рода.
ИМХО.
это задача на собеседование? интересно что нею хотели выяснить :)))
ReplyDeleteв реальность такой код даже обкуреный индус не напишет, а как способ проверить понимание взаимосвязи указателей и масивов оно просто тупо выглядит. имхо language laweyrs которые такое пробуют спрашивать нужно сразу нафиг посылать.
2_winnie:
ReplyDeleteВо-первых, по стандарту не совсем валидный - выход указателя за начало массива. array-1+2 это не array+2-1, в первом выражении ошибка, во втором нет.
а по моему ошибки нету ни там ни там. в С++ кажись нет проверки выхода указателя за границу массива
2Андрей Валяев:
по поводу кода - это всего лишь предположение. Компиляторы все навороченнее становятся и я думаю, что подобный шанс оптимизировать их разработчики все таки не упустят.
2cencio:
нет, это реальный код, чисто машинально перепутали местами argc и argv, при ревью это обнаружили и зависли, так как код работал корректно.
Не люблю C++ за то, что приходится думать не только на уровне языка, но и на уровень ниже. Иначе побочные эффекты неизбежны.
ReplyDeleteну ситуации подобные описанной в посте возникают на самом деле редко ))
ReplyDeleteГде-то у Саттера на эту тему было. Тянется это правило из C - x[y] раскрывается в *(x+y), при этом там должны быть указаталь и целое число в любом порядке.
ReplyDeleteМожно получить такой забавный код:
int x[3] = { 11, 12, 13 };
cout << 0[x] << endl;
И он скомпилируется и будет работать.
Хотя это и допустимо стандартом, писать так никогда не стоит в реальном коде, т.к. будет запутывать непосвещенных...
Я заранее извиняюсь, если что недосмотрел, но я постарался внимательно прочитать комменты.
ReplyDeleteКажется, никто не увидел ошибку в ответе...
Как сказал jia3ep, такой код работает, потому что это вытекает из принципа работы оператора []: запись a[i] эквивалентна *(a + i) или *(i + a).
А у Вас в ответе совсем не то же самое:
cout<<(*argv+(argc-1))<<endl<<((argc-1)+*argv)
Должно быть так:
cout<< *(argv+(argc-1))<<endl<< *((argc-1)+argv)
2White Knight: совершенно верно, большое спасибо, исправлю
ReplyDeleteДолго думал и экспериментировал.
ReplyDeleteСудя по всему получается так, что результат сложения указателя и целого всегда имеет тип указателя.
проходят даже выражения типа
BOOST_ASSERT (argv + argc - 1 == argc + argv - 1);
то есть судя по всему, компилятор автоматически переворачивает выражения типа (int) + (ptr).
Хоть бы варнинг чтоль какой нибудь сделали на эту тему. Имхо так писать не стоит.
Если не ошибаюсь этот пример с указателями и массивами есть ещё в книжке Кернигана и Ричи 1978 г.
ReplyDeleteКроме того, всегда думал что при сложении вообще говоря тип суммы совпадает с самым широким типом операндов: int+double=double, int+char=int и т.д. Аналогичнно int+ptr=ptr.
Вот, кстати, кусок из стандарта:
3.3.6 Additive operators
...
When an expression that has integral type is added to or subtracted
from a pointer, the integral value is first multiplied by the size of
the object pointed to. The result has the type of the pointer
operand.
...
Просто по-другому бессмысленно делать.
Код рабочий
ReplyDeleteкомментарии)
ReplyDelete