Comments 32
В Паскале используется "&" для оператора взятия адреса, "^" для определения ссылочного типа, "^" для взятия значения по ссылке.
В С используется "&" для оператора взятия адреса, "*" для определения ссылочного типа, "*" для взятия значения по ссылке.
В Паскале используется "&" для оператора взятия адреса,
Вообще-то '@'.
Но вопрос тот-же про «и там и там используется ^», что имеется ввиду?
PMyType = ^TMyType - указатель на TMyType
var
abc: PMyType;
...
:= abc^.field;
Паскале — нет, мнемоники никакой нет.
Почему нет? Вроде, есть. Не везде допустима, но есть.
var
abc: ^Integer;
...
abc^ := 123;
В сях будет так:
int *a
Т.е. «если дереференснуть а, будет int», потому что * перед — это разыменовывание. Так что такое объявление логично.
А паскалевское такой мнемоники не имеет — каретка прыгает то перед переменной, то после.
Если было бы ^abc = было бы так же мнемоничненько.
Тип с крышкой (^) впереди — это ссылочный тип. Все просто и понятно.
В сях есть мнемоника, которая не требует запоминания.
Тип с крышкой (^) впереди — это ссылочный тип. Все просто и понятно
Нет, каретка ставится перед тем, на что ссылку описываем, а не перед самим типом. Никакой логикой тут не пахнет.
PMyType = ^TMyType;
Ссылочный тип тут PMyType, а не TMyType. Было бы PMyType = TMyType^; было бы как в Си, логично. Но низя: требование однопроходности паскалевского компилятора. Не скажу, что я не доволен этим балансом, tradeoffом так сказать.
В сях есть мнемоника, которая не требует запоминания.
Ну как не требует запоминания? Программисты на C святым духом что ли пользуются?
Независимо от того, где ставится крышка/звездочка это требует запоминания. Или только слева, или только справа, или одно слева, другое справа.
Ссылочный тип тут PMyType, а не TMyType.
Ссылочный тип здесь ^TMyType и, по счастливой случайности, PMyType.
Ну как не требует запоминания? Программисты на C святым духом что ли пользуются?
Независимо от того, где ставится крышка/звездочка это требует запоминания. Или только слева, или только справа, или одно слева, другое справа.
Вы все равно не поняли, или я выразился неточно. В С запоминается только одно: звездочка и потом переменная — это разыменовывание. Всё. В Паскале запоминается как синтаксис разыменовавания, так и синтаксис объявления, потому что он нелогичен. В С синтаксис объявления органично следует из синтаксиса разыменовывания.
typedef int (*fun)(void*);
тут уже надо вводить курсы чтения таких конструкций с размоткой внутрь структуры определения, молодёжь не понимает, да и аксакалы иногда спотыкаются на особо сложных заворотах.
А вот такое компиляторы уже не осиливают, выдавая ошибки:
typedef int (*fun1)(int) (*fun2)(int);
хотя, казалось бы, чего сложно — определить fun2 как указатель на функцию с int параметром и указателем на функцию int->int в результате…
ан нет, случился зашкал сложности, и без промежуточных typedefʼов не выкрутиться.
Стиль таких определений в Pascal, Go (*) и многих других — читаемее и не страдает такими ограничениями. Цена же за это — что надо, например, явно писать слова var — для переменных, func — для определений функций. Как по мне, цена вполне разумная, польза перевешивает.
(*) Хоть я его и громко ругаю за кучу прочего.
А вот такое компиляторы уже не осиливают, выдавая ошибки:
typedef int (*fun1)(int) (*fun2)(int);
Компилятор как раз осилит, а вот мы, люди, такое можем в голове и не удержать. :)
… определить fun2 как указатель на функцию с int параметром и указателем на функцию int->int в результате
typedef int (*(*fun1)(int))(int);
Вот здесь дан очень хороший ответ:
stackoverflow.com/questions/10758811/c-syntax-for-functions-returning-function-pointers
От себя добавлю, что очень помогает
typeof
, если он определён компилятором:typedef typeof(typeof(int(*)(int)) (*)(int)) fn2;
// Если нет аллергии на макросы:
#define FN(PARAMS, RET) typeof(RET(*) PARAMS)
typedef FN((int), FN((int), int)) fn3;
Вы определили fun1, а не fun2. Похоже, это опечатка — если написать fun2, то получится то, что ожидается (?)
Вот об этом я и говорю — если в спокойной обстановке тут путаешься в таких определениях, то что же будет в нормальном рабочем цейтноте?
А ещё — сколько времени прошло, прежде чем хоть кто-то заметил, где я ошибся?
> Вот здесь дан очень хороший ответ:
Угу, с тремя слоями вкручивания в определение…
> typedef typeof(typeof(int(*)(int)) (*)(int)) fn2;
и ещё одна путаница, и уже другого стиля (!) Итого — два разных стиля.
Спасибо за исправление моего примера. И всё это считаю безусловными примерами в пользу того, что синтаксис определений в том порядке, как Pascal/Ada/Go/etc., работает на пользу написания/чтения/сопровождения, а синтаксис C — против.
Я хотел ответить здесь чтобы любой, кто прочтёт ваш комментарий, не был введён в заблуждение. Нет «ограничения» данной нотации в плане функциональности.
Вы определили fun1, а не fun2. Похоже, это опечатка — если написать fun2, то получится то, что ожидается (?)
Да, получится то, что вы описывали.
Это, кстати, не самый удивляющий пример.
Без typedef определить функцию, возвращающую функцию, которая возвращает и принимает функцию — вот тут в скобочках и звёздочках нормальные люди начнут «плавать».
и ещё одна путаница, и уже другого стиля (!) Итого — два разных стиля.
Это способ описания указателей на функции без надобности запоминать странные правила их определения.
Не уверен, что понял, о какой «путаннице» вы говорили.
С этим макросом стиль определения будет чем-то похож на тот, что в Go:
FN((int), int)
=
func(int) int
FN(FN((int, string), float), FN(int) int)
=
func(func(int, string) float) func(int) int
Если я допущу здесь какую-то опечатку или пропущу запятую — не вижу в этом катастрофы. Описана идея, а не исполняемые сниппеты.
Помнится, Спольски разорялся по поводу языков, в которых нет указателей (ну и рекурсию сюда же прицепил) http://local.joelonsoftware.com/wiki/Опасности_обучения_на_Java
дизайнеры
Designer = разработчик, или в данном контексте «создатель» (языка программирования). Дизайнер — тот, кто создает одежду или интерьер для квартиры, такова коннотация в русском яыке.
Это будет выглядеть примерно как в Rust, или, если позволите, «не как в Си».
Вы ведь не придерживаетесь мнения, что для высокоуровневого кода арифметика указателей полезна?
Это может быть полезно при работе, например, с сишными библиотеками.
Point в том, что там, где реально нужно использовать арифметику указателей, делать это можно. Тезис «людям не нравится адресная арифметика» не обоснован,
корректнее сказать: «большинству не нравится адресная арифметика там, где можно было обойтись без неё».
P.S. — если кто-то этим воспользуется, стоит мониторить
proposal: spec: disallow T<->uintptr conversion for type T unsafe.Pointer
Это, как минимум, сильно усложняет жизнь Garbage Collector'у и не позволяет компилятору вставить за вас проверки на выходы за границы массива, например.
Подскажите пожалуйста одну вещь. В своё время я задавал вопрос, почему существует такая вещь как not-so-nil interface pointers
— ситуация, когда nil-указатель на переменную конкретного типа неявно приводится к указателю на интерфейс, который этот тип реализует. И, как результат, у нас получается fat pointer с nil data pointer и валидным vtable pointer. Следствие — nil pointer dereference в рантайме, где его не ждали. Но вопрос не в этом. Вопрос в том, что ответ тогда был "потому что в Go указатели, а не ссылки". Как раз этот ответ я и не понял.
Мне в общем-то тоже понятно, почему так происходит. Мне даже понятно, для чего используется эта фича. Что мне, к сожалению, осталось непонятно — почему это разрешено в виде неявного преобразования. Как по мне, сильно повышает шансы наступить на грабли. Было бы в виде явного тайп каста, так что неявный приводит к fully nil interface pointer — вопросов бы не было. Впрочем, как реализовали так реализовали. Может в v2 поменяют. Или давно есть линт, о котором я не знаю.
Вырожденный пример будет какой-то такой
// Note: both FirstError and SecondError implement 'error' interface
// and use some data from value when their respective Error() is called
// May return non-nil error, if code fails
func first(arg MyData) *FirstError { /* some code here */ }
// May also return non-nil error, if code fails
func second(arg MyData) *SecondError { /* some other code here */ }
func third(arg MyHugeData) error {
if err := first(arg.DataField); err != nil {
return err
}
return second(arg.DataField)
}
func fourth(arg MyHugeData) {
if err := third(arg); err != nil {
fmt.Println(err.Error())
}
}
Такой код КМК вполне может случиться — "просто верни статус последней операции дальше по стеку". Во-первых, результат всегда будет трактоваться как неуспех. Во-вторых, при попытке узнать причину мы получим nil-pointer panic из по сути ниоткуда. Пример с обработкой ошибок КМК просто будет самый типичный. Я в своё время нарвался на такое поведение в другом контексте, подробностей уже не помню.
Но суть была именно такой:
- Есть несколько типов, приводимых к одному интерфейсу.
- Каждый из типов требует не-нулевой инстанс чтобы корректно реализовывать указанный интерфейс
- В некоем коде, допустим при передаче через канал, указатель на объект какого-либо из конкретных типов приводится к интерфейсу. При этом для канала nil pointer является абсолютно корректным положением вещей и обрабатывается на принимающей стороне как положено.
- На принимающей стороне не-нулевой указатель на интерфейс неожиданно оказывается очень даже нулевым, т.к. сам по себе vtable бесполезен. Результат — NPE где не ждали.
Ошибка тут, конечно очевидная — возврат ошибки по значению, а не с помощью интерфейса error из First и Second. Тоесть — это как бы и не проблема (с точки зрения компилятора), но это немного самодельный способ обработки ошибки — «А если first еще и другой тип ошибки может вернуть, а не только *FirstError)?».
Пример с обработкой ошибок просто самый наглядный. Во второй половине комментария описан чуть более сложный случай. Добавьте туда пункт 2.5:
Функции, возвращающие инстансы конкретных типов, используются в других местах модуля для получения этих инстансов, но без необходимости приводить указатели к единому интерфейсу.
Проблема именно в том, что в сколь-нибудь нетривиальном коде вполне можно получить "битый интерфейс" на ровном месте.
Поэтому решение включить указатели (без арифметики указателей, к счастью) в Go с тем же синтаксисом — было вполне логичным и естественным.
Если нет арифметики указателей, по мне было бы логичнее использовать два ключевых слова — для типов по ссылке и для типов по значению. Скажем class и record.
Указатель на один тип данных можно разыменовывать как на другой в Go? Если да, то вообще решение убрать арифметику будет странным.
Если нет, то можно было бы определить типы «указателей на» и «значений» и все.
var a int
var b pInt
pInt = class int
Зачем в Go амперсанд и звёздочка (& и *)?