вопрос:
в С заявляется, что производными типами являются структуры, массивы и функции
то есть, получается, функции являются значениями
тогда опять же, получается, что запись
int f(int);
должна означать создание переменной типа функции, с одним целым параметром и целым возвращаемым значением
тогда, почему недопускается следующая запись:
int g(int i){return i;}
f = g;// ошибка
и второй вопрос, почему, если объявляется указатель на функцию, то есть
int (*f)(int);
то валидна запись
f = g;
и при этом она оказывается равносильной
f = &g;
Так являются функции отдельным типом или это какие-то специальные объекты со своими правилами ??
рассматривай как const тогда
__>и второй вопрос, почему, если объявляется указатель на функцию, то есть
__>int (*f)(int);
__>то валидна запись
__>f = g;
могу ошибаться, но это в стиле С __>f = &g;
a это в стиле С++
в современных С++ компиляторах оставлено для поддержки С
Здравствуйте, _hum_, Вы писали:
__>Так являются функции отдельным типом или это какие-то специальные объекты со своими правилами ??
Функции — это специальные объекты (точнее, они не являются объектами). Да, для них действуют свои правила.
Как, кстати, и массивы.
В синтаксисе и в системах типов С/С++ есть хитрости.
Так, функция может быть приведена к указателю на функцию, а массив — к указателю на элемент.
Поэтому
Здравствуйте, Мухоморец, Вы писали:
М>Здравствуйте, _hum_, Вы писали:
__>>int f(int);
__>>int g(int i){return i;}
__>>f = g;// ошибка
М>рассматривай как const тогда
Тогда, получается, что переменной типа "функция" в С нет (есть тип, но переменной этого типа нет — только константы)
А тогда запись int f(int); должна означать не определение, а какое-то действие иного характера, типа декларирование для компилятора. Но ведь в struct{ int f(int); } X; эта же запись означает определение (под указатель на функцию отводится место в памяти, насколько я понимаю). Получается одна и та же запись в разных местах означает различное..
__>>и второй вопрос, почему, если объявляется указатель на функцию, то есть
__>>int (*f)(int);
__>>то валидна запись
__>>f = g;
М>могу ошибаться, но это в стиле С __>>f = &g; М>a это в стиле С++ М>в современных С++ компиляторах оставлено для поддержки С
М>а ля М>int m[10]; М> m == &m;
__>Тогда, получается, что переменной типа "функция" в С нет (есть тип, но переменной этого типа нет — только константы)
ну ты же можешь обьявить указатели на функцию
значит тип(ы) есть
мы же можем сказать какой тип у указателя на функцию
__>А тогда запись int f(int); должна означать не определение, а какое-то действие иного характера, типа декларирование для компилятора. Но ведь в struct{ int f(int); } X; эта же запись означает определение (под указатель на функцию отводится место в памяти, насколько я понимаю). Получается одна и та же запись в разных местах означает различное..
что глобал функция, что метод в памяти находятся единожды
толко при вызове метода ему передаётся this объекта )), чтоб знал с чьими мемберами она работает
то что в структуре описано 100 методов не виртуальных
так это на размер объектов никак не влияет
Здравствуйте, _hum_, Вы писали:
__>Тогда, получается, что переменной типа "функция" в С нет (есть тип, но переменной этого типа нет — только константы)
Именно. В С/С++ функции — не "первоклассные объекты".
__>А тогда запись int f(int); должна означать не определение, а какое-то действие иного характера, типа декларирование для компилятора. __>Но ведь в struct{ int f(int); } X; эта же запись означает определение (под указатель на функцию отводится место в памяти, насколько я понимаю).
Неправильно понимаешь. Здесь ты объявил функцию-член у анонимной структуры. (Интересно, как ты её будешь определять )
Объявление переменной типа "указатель на функцию" — это всегда result(*f)(args).
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, _hum_, Вы писали:
__>>Тогда, получается, что переменной типа "функция" в С нет (есть тип, но переменной этого типа нет — только константы)
К>Именно. В С/С++ функции — не "первоклассные объекты".
О, это уже больше похоже на правду, где бы поподробнее про эту классификацию узнать?
__>>А тогда запись int f(int); должна означать не определение, а какое-то действие иного характера, типа декларирование для компилятора. __>>Но ведь в struct{ int f(int); } X; эта же запись означает определение (под указатель на функцию отводится место в памяти, насколько я понимаю).
К>Неправильно понимаешь. Здесь ты объявил функцию-член у анонимной структуры. (Интересно, как ты её будешь определять )
Почему ж неправильно, и, кстати, я объявлял не анонимную структуру, а переменную X заданного типа. Можно, по-другому, например,
struct T{
int i;
int g(int);
};
T X;
X.i = 0;
будет работать и без объявления тела функции g, поскольку память под int g(int) отводить известно сколько.
К>Объявление переменной типа "указатель на функцию" — это всегда result(*f)(args).
Здравствуйте, _hum_, Вы писали:
__>О, это уже больше похоже на правду, где бы поподробнее про эту классификацию узнать?
Ну, погугли на слово first class objects...
Да, кстати. Ты на каком языке раньше писал? Смолток, Яваскрипт, Питон?
К>>Неправильно понимаешь. Здесь ты объявил функцию-член у анонимной структуры. (Интересно, как ты её будешь определять ) __>Почему ж неправильно, и, кстати, я объявлял не анонимную структуру, а переменную X заданного типа.
Вот это и показывает, что ты понимаешь неправильно.
Ты объявил тип "анонимная структура" и тут же объявил переменную этого типа.
__> Можно, по-другому, например,
__>struct T{
__> int i;
__> int g(int);
__> };
__> T X;
__> X.i = 0;
__>будет работать и без объявления тела функции g, поскольку память под int g(int) отводить известно сколько.
Именно. И знаешь, сколько памяти отведено под int g(int)? РОВНО НОЛЬ!!!
Имена (ну, или объявления) функций — это, считай, литералы, ссылающиеся (!) (не указывающие, а именно ссылающиеся) на куски кода.
Сколько памяти отведено под число 123? А под число 123+456? А если это число упомянуто в программе несколько раз?
Литерал не является объектом. Где компилятор захочет его подставить — там и подставит.
Если ты написал конструктор
struct T
{
T() { cout << 123; }
int g() { return 456; }
};
это не значит, что в каждом экземпляре класса T будет своя копия конструктора или указателя на конструктор, и уж тем более, что там будут копии чисел 123 и 456.
Всё это просто зашито в код.
Точно так же зашиты в код вызовы функции-члена.
Потому что С задумывался как высокоуровневый ассемблер, а не как функциональный язык. Как следствие он обладает слабой типизацией, а это значит, что система типов не имеет в С сколько-нибудь серъезного влияния, как, например, в С# или Python.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, _hum_, Вы писали:
__>>О, это уже больше похоже на правду, где бы поподробнее про эту классификацию узнать?
К>Ну, погугли на слово first class objects...
К>Да, кстати. Ты на каком языке раньше писал? Смолток, Яваскрипт, Питон?
К>>>Неправильно понимаешь. Здесь ты объявил функцию-член у анонимной структуры. (Интересно, как ты её будешь определять ) __>>Почему ж неправильно, и, кстати, я объявлял не анонимную структуру, а переменную X заданного типа. К>Вот это и показывает, что ты понимаешь неправильно. К>Ты объявил тип "анонимная структура" и тут же объявил переменную этого типа.
__>> Можно, по-другому, например, К>
__>>struct T{
__>> int i;
__>> int g(int);
__>> };
__>> T X;
__>> X.i = 0;
К>
__>>будет работать и без объявления тела функции g, поскольку память под int g(int) отводить известно сколько. К>Именно. И знаешь, сколько памяти отведено под int g(int)? РОВНО НОЛЬ!!!
К>Имена (ну, или объявления) функций — это, считай, литералы, ссылающиеся (!) (не указывающие, а именно ссылающиеся) на куски кода. К>Сколько памяти отведено под число 123? А под число 123+456? А если это число упомянуто в программе несколько раз? К>Литерал не является объектом. Где компилятор захочет его подставить — там и подставит.
К>Если ты написал конструктор К>
К>struct T
К>{
К> T() { cout << 123; }
К> int g() { return 456; }
К>};
К>
К>это не значит, что в каждом экземпляре класса T будет своя копия конструктора или указателя на конструктор, и уж тем более, что там будут копии чисел 123 и 456. К>Всё это просто зашито в код. К>Точно так же зашиты в код вызовы функции-члена.
Действительно, нуль...Значится, я на ентот счет ошибался...
А еще, может быть подскажете, как лучше и проще оформить правила вывода типов. Сам пока я токо прикинул,
что хорошо бы было что-нибудь типа:
пусть Type — известный тип, NewType — определяемый, тогда
type_definition(NewType) ::= typedef Type Op(NewType) |
typedef struct NewType '{'Type1 name1';'...'Typen namen''}' |
typedef union NewType '{'Type1 name1';'...'Typen namen''}'
ArrayOp(NewType) ::= NewType'['n']' // массив из...
PointerOp(NewType) ::= '*'NewType // указатель на...
FuncOp(NewType) ::= NewType(Type1','...','Typen) // функция с аргументами типа Type1,..,Typen возвращающая...
IdOp(NewType) ::= NewType | '('NewType')'
При следующих условиях:
— не должно получаться массивов из функций
— не должно получаться функций, возврщающих функции
— не должно получаться функций, возвращающих массивы
И далее, правильно ли так говорить, что значения типов функций и значения ссылочных типов (кстати, куда бы их в сиснтаксисе всунуть) не являются значениями первого класса (для них, например, не могут создаваться переменные).
<Оверквотинг вырезан. Пожалуйста, цитируй только существенное.>
__>А еще, может быть подскажете, как лучше и проще оформить правила вывода типов. Сам пока я токо прикинул, __>что хорошо бы было что-нибудь типа:
__>пусть Type — известный тип, NewType — определяемый, тогда
__>При следующих условиях: __>- не должно получаться массивов из функций __>- не должно получаться функций, возврщающих функции __>- не должно получаться функций, возвращающих массивы
Эти условия как раз обеспечены тем, что у тебя typedef Type Op(NewType).
Потому что в С/С++ объявления функций и массивов — хитровывернутые. Вот если бы было в стиле паскаля (а также хаскеля, ML и т.д.) type NewType = Op(Type)...
Кстати, а чем они тебе помешали? Ну пусть функция возвращает функцию. Жалко, что ли?
Или это вопрос — как грамматику составить? Дык, просто. Только она будет уже не LL(1)
В С/С++ объявление можно представить в виде prefix Name suffix, где префикс и суффикс — какие-то строки, вместе образующие правильное выражение (сбалансированное по скобкам и т.д.)
Соответственно, производные типы получаются присобачиванием операторов (*, &, const, volatile, (), []) к префиксу/суффиксу.
__>И далее, правильно ли так говорить, что значения типов функций и значения ссылочных типов (кстати, куда бы их в сиснтаксисе всунуть) не являются значениями первого класса (для них, например, не могут создаваться переменные).
Семантика того, что является или не является первоклассными объектами — не зависит от синтаксиса типов.
Например, в C# массивы — первоклассные объекты.
Это во многом обусловлено рантаймом, который у С/С++ сознательно прибеднён.
Почему-то не увидел отправленного мной ранее сообщения, поэтому повторюсь.
Действительно, я ошибался, думая, что при декларировании в структуре функции там же отводится память под указатель на эту функцию. Хотя, как по мне, так это было бы проще для восприятия семантики языка (механизм виртуальных функций-то, по сути, так и реализован).
Думаю, тот факт, что функции не являются объектами первого класса, во многом объясняет ситуацию, и снимает мой первоначальный вопрос.
Вот тут еще появился вопросик. Как по-вашему можно было бы записать правила вывода типов в C/C++. Хотелось бы что-нить вроде BNF-формы. Что-нить типа:
Пусть
Type — известный тип (базовый, или уже определенный по этой схеме ранее),
NewType — определяемый.
Тогда определение типа имеет вид
type_definition(NewType) ::= typedef Type Op(NewType) |
typedef struct NewType '{'Type1 name1';'...Typen namen';''}' |
typedef union NewType '{'Type1 name1';'...Typen namen';''}'
ArrOp(NewType) ::= NewType'['n']' // массив из...
PtrOp(NewType) ::= '*'NewType // указатель на...
FunOp(NewType) ::= NewType(Type1','...','Typen) // функция с арг. типа Type1,..,Typen возвращающая...
IdOp(NewType) ::= NewType | '('NewType')'
При следующих условиях:
— не должно получаться массивов из функций
— не должно получаться функций, возвращающих функции
— не должно получаться функций, возвращающих массивы
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, _hum_, Вы писали:
__>>При следующих условиях: __>>- не должно получаться массивов из функций __>>- не должно получаться функций, возврщающих функции __>>- не должно получаться функций, возвращающих массивы
К>Эти условия как раз обеспечены тем, что у тебя typedef Type Op(NewType).
Как же эти условия обеспечены, если нчиегомне не запрещает написать typedef int (T(int))[10]
К>Потому что в С/С++ объявления функций и массивов — хитровывернутые. Вот если бы было в стиле паскаля (а также хаскеля, ML и т.д.) type NewType = Op(Type)...
Объявления, по-моему, ничуть не вывернутей, чем других типов — подчиняются общим правилам -см. ранее попытку формализовать эти правила.
К>Кстати, а чем они тебе помешали? Ну пусть функция возвращает функцию. Жалко, что ли?
Это запрещено в С и С++
К>Или это вопрос — как грамматику составить? Дык, просто. Только она будет уже не LL(1)
Да,именно, как записать соответствующие правила в виде формальной грамматики (обычной или атрибутивной?)
К>В С/С++ объявление можно представить в виде prefix Name suffix, где префикс и суффикс — какие-то строки, вместе образующие правильное выражение (сбалансированное по скобкам и т.д.)
Что значит "какие-то строки, образующие правильные выражения" — что именно считается правильным — в этом же и вопрос!
К>Соответственно, производные типы получаются присобачиванием операторов (*, &, const, volatile, (), []) к префиксу/суффиксу.
То есть *prefix Name *suffix будет считаться правильным — какой же в этом случае должны быть prefix и suffix, интересно...
__>>И далее, правильно ли так говорить, что значения типов функций и значения ссылочных типов (кстати, куда бы их в сиснтаксисе всунуть) не являются значениями первого класса (для них, например, не могут создаваться переменные).
К>Семантика того, что является или не является первоклассными объектами — не зависит от синтаксиса типов.
Вопрос же был не так поставлен. Спрашивалось, можно ли считать, что ссылочные типы в С/C++, как и функции, не являются объектами первого класса.
К>Например, в C# массивы — первоклассные объекты. К>Это во многом обусловлено рантаймом, который у С/С++ сознательно прибеднён.
Здравствуйте, _hum_, Вы писали:
__>Действительно, я ошибался, думая, что при декларировании в структуре функции там же отводится память под указатель на эту функцию. __>Хотя, как по мне, так это было бы проще для восприятия семантики языка (механизм виртуальных функций-то, по сути, так и реализован).
Семантика С/С++ насквозь пропитана идеей минимализма. Ни одного лишнего байта, насколько это возможно.
А механизм виртуальных функций реализован по-другому. Обычно в теле объекта — не тыщща указателей на отдельные функции, а указатель на таблицу этих указателей.
Я не понимаю всё-таки, что ты хочешь выяснить.
— Как устроены синтаксис и семантика реальных языков C|C++? --> читай Стандарт, там синтаксис освещён.
— Как сделать собственный язык "по мотивам", обладающий нужными свойствами?
— Определиться с комплектом свойств, отличающих его от оригинала?
— Как сделать собственный парсер?
Или это попытка подойти к С/С++ как к чёрному ящику, а в качестве входов-выходов — расспрашивать экспертов?
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, _hum_, Вы писали:
К><>
К>Я не понимаю всё-таки, что ты хочешь выяснить. К>- Как устроены синтаксис и семантика реальных языков C|C++? --> читай Стандарт, там синтаксис освещён. К>- Как сделать собственный язык "по мотивам", обладающий нужными свойствами? К>- Определиться с комплектом свойств, отличающих его от оригинала? К>- Как сделать собственный парсер? К>Или это попытка подойти к С/С++ как к чёрному ящику, а в качестве входов-выходов — расспрашивать экспертов?
Ситуация такова — нужно другого человека вкратце ознакомить, что за штука язык С/C++ и с чем его едят.
Понятно, что "вкратце" требует, чтоб рассказ шел системно, с общих позиций. Вот тут-то при попытке составить это системное видение и возникают проблемы. Первая из них — определение типов и переменных данных типов, что собственно и явлилось темой эхи. Как я понял из наших разговоров, заковырки здесь появляются из-за того, что в каждом языке есть понятие первоклассных и нет объектов, и наличие вторых мешает строить общие правила. Вроде, понятно... Второй вопрос, который возникает — это как, с учетом наличия непервоклассных объектов, объяснить правила, по которым могут выводиться типы. В Стандарте к языку есть нечто подобное, но слишком все-таки расплывчатое с кучей комметов и примеров... Я хотел выяснить у Вас, какими по возможности формальными правилами Вы пользуетесь.
Здравствуйте, _hum_, Вы писали:
__>Ситуация такова — нужно другого человека вкратце ознакомить, что за штука язык С/C++ и с чем его едят.
Во, это другое дело! Сразу ясен контекст.
__>Понятно, что "вкратце" требует, чтоб рассказ шел системно, с общих позиций. Вот тут-то при попытке составить это системное видение и возникают проблемы. __>Первая из них — определение типов и переменных данных типов, что собственно и явлилось темой эхи.
__>Как я понял из наших разговоров, заковырки здесь появляются из-за того, что в каждом языке есть понятие первоклассных и нет объектов, и наличие вторых мешает строить общие правила. Вроде, понятно...
Тут нужно всё-таки твёрдо уяснить. Есть синтаксис и есть семантика.
Синтаксис никаким образом не определяет, являются те или иные сущности первоклассными объектами или нет.
Семантика, конечно, частично зависит от синтаксиса: скажем, если не предусмотрено такой языковой конструкции, как ссылка на ссылку — то, хоть тресни, её не получишь. Ну, неявно или симуляцию — это пожалуйста...
__>Второй вопрос, который возникает — это как, с учетом наличия непервоклассных объектов, объяснить правила, по которым могут выводиться типы.
Непервоклассные объекты невозможно создавать в рантайме, но они могут создаваться во время компиляции.
Так, например, ты не можешь создать массив произвольного размера, чтобы тип его был именно "массив". new T[N] — имеет тип T*.
А функцию — вообще не можешь создать (впрочем, с использованием хаков — генерируя машинный код — пожалуйста).
Но во время компиляции-то они создаются! И типы имеют!
Вот и всё объяснение.
Дальше нужно курить синтаксис.
__>В Стандарте к языку есть нечто подобное, но слишком все-таки расплывчатое с кучей комметов и примеров... Я хотел выяснить у Вас, какими по возможности формальными правилами Вы пользуетесь.
Опытом
Есть правила разбора выражений типа. Правила конструирования — это обратные функции.
Примерно вот так сишные выражения типа превращаются в чисто префиксные.
type expr ---> type expr
type (expr) ---> type expr
type expr [dim] ---> array(type,dim) expr
type expr (args) ---> function(type,args) expr
type const expr ---> const(type) expr
const type expr ---> const(type) expr
type *expr ---> pointer(type) expr
Например,
int x [10][20][30] =
array(int,30) x [10][20] =
array(array(int,30),20) x [10] =
array(array(array(int,30),20),10) x
т.е. массив из 10 элементов, каждый из которых — массив из 20 элементов, каждый из которых, наконец — массив из 10 int'ов.
Ну и наоборот:
array(pointer(function(pointer(int),char)),10) tbl - таблица из 10 указателей на функции вида int* <- char
= pointer(function(pointer(int),char)) tbl[10]
= function(pointer(int),char) *tbl[10]
= pointer(int)(*tbl[10])(char)
= int*(*tbl[10])(char)
Вот примерно так.
Скобки возникают в тех случаях, когда нужно разрулить кажущуюся неоднозначность (для компилятора-то её нет, а программист мозг сломает).
Постфиксные операторы — [], () — приоритетнее префиксных — *, const.
Поэтому
int** x[10] =
array(int**,10) x =
array(pointer(pointer(int)),10) x
int* (*x) [10] =
array(int*,10) *x =
pointer(array(pointer(int),10)) x
int (**x) [10] =
array(int,10) **x =
pointer(pointer(array(int,10))) x
Здравствуйте, Кодт, Вы писали:
К>Непервоклассные объекты невозможно создавать в рантайме, но они могут создаваться во время компиляции. К>А функцию — вообще не можешь создать (впрочем, с использованием хаков — генерируя машинный код — пожалуйста).
А как же boost::lambda?
Здравствуйте, Посторонним В., Вы писали:
К>>Непервоклассные объекты невозможно создавать в рантайме, но они могут создаваться во время компиляции. К>>А функцию — вообще не можешь создать (впрочем, с использованием хаков — генерируя машинный код — пожалуйста). ПВ>А как же boost::lambda?
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Посторонним В., Вы писали:
К>>>Непервоклассные объекты невозможно создавать в рантайме, но они могут создаваться во время компиляции. К>>>А функцию — вообще не можешь создать (впрочем, с использованием хаков — генерируя машинный код — пожалуйста). ПВ>>А как же boost::lambda?
К>А какой тип получается у этой самой буст-лямбды?
Я не имел ввиду что boost::lambda создает какой-либо объект, тем более объект первого класса.
Я имел ввиду что boost::lambda позволяет создавать что-то похожее на функцию в runtime.