LINUX.ORG.RU

Функции внутри функций в Си


0

1

Попал мне в руки код GRUB'а.

grub_device_iterate (int (*hook) (const char *name))
{
  auto int iterate_disk (const char *disk_name);

  struct part_ent
  {
    struct part_ent *next;
    char *name;
  } *ents;

  int iterate_disk (const char *disk_name)
    {
      grub_device_t dev;

      if (hook (disk_name))
	return 1;

      dev = grub_device_open (disk_name);
      if (! dev)
	return 0;
	.................
	}
	................
}
Можете пояснить, зачем в начале функция iterate_disk объявляется со спецификатором auto? Я такого никогда не встречал, даже в книжках.


auto

Defines a local variable as having a local lifetime.

Keyword auto uses the following syntax:

[auto] data-definition;

As the local lifetime is the default for local variables, auto keyword is extremely rarely used.

Note: GNU C extends auto keyword to allow forward declaration of nested functions.

A nested function always has internal linkage. Declaring one with extern is erroneous. If you need to declare the nested function before its definition, use auto (which is otherwise meaningless for function declarations).

int bar (int *array, int offset, int size)
{
  __label__ failure;
  auto int access (int *, int);
  /* ... */
  int access (int *array, int index)
    {
      if (index > size)
        goto failure;
      return array[index + offset];
    }
  /* ... */
}
shty ★★★★★
()

Что-то в последнее время на лоре все реже и реже вспоминают про маргинальные языки... Чаще вспоминают С и C++, вот она суровая реальность...

anonymous
()
Ответ на: комментарий от anonymous

Просто почти всех интересных людей выгнали.

anonymous
()

4.4 Вложенные Функции

Вложенная функция - это функция, определенная внутри другой функции. Имя вложенной функции является локальным в блоке, где она определена. Например, здесь мы определяем вложенную функцию с именем square и вызываем ее дважды:

  foo (double a, double b)
          {
            double square (double z) { return z * z; }
     
            return square (a) + square (b);
          }
Вложенная функция имеет доступ ко всем переменным объемлющей функции, которые видны в точке ее определения. Это называется «лексическая область действия». Например, ниже мы показываем вложенную функцию, которая использует наследуемую переменную с именем offset:

          bar (int *array, int offset, int size)
          {
     
            int access (int *array, int index)
     
              { return array[index + offset]; }
     
            int i;
     
            ...
     
            for (i = 0; i < size; i++)
     
              ... access (array, i) ...
          }

Определения вложенных функций разрешаются внутри функций, где допустимы определения переменных; то есть в любом блоке перед первым оператором в блоке.

http://docstore.mik.ua/manuals/ru/gcc/gcc1-4.html

drBatty ★★
()
Ответ на: комментарий от drBatty

нафик такой изврат ? какие то костыли что мы имеем ? ограничение области видимости глобальных переменных используемых обеими функциями но разве не для этого предназначены глобальные переменные с внутренним связыванием и отдельная единица трансляции ? зачем вводить лишние сущности ? тем более такие костыли так как реально они не нужны ну разве что паскалистам перешедшим на си нехватает такой хрени

anonymous
()
Ответ на: комментарий от anonymous

>нафик такой изврат ?

перед коллегами понтоваться же.

для работы за 15+ лет ни разу не надо было. просто как-то встретил в чужом (быдло)коде.

drBatty ★★
()
Ответ на: комментарий от drBatty

А я частенько внутри функций делаю вложенные inline-функции, если хочется улучшить читабельность кода и не копипастить 100500раз одно и то же.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

>А я частенько внутри функций делаю вложенные inline-функции, если хочется улучшить читабельность кода и не копипастить 100500раз одно и то же.

ИМХО можно сделать внешнюю неинлайновую функцию. gcc сейчас умеет их инлайтить как я понял.

drBatty ★★
()
Ответ на: комментарий от Eddy_Em

а никто не смотрел куда компилятор пихает разделяемые переменные ? и как вложенная функция до них дотягивается ? как глобальные переменные не может быть из за многопоточности тогда выходит вложенная функция как то хитро дотягивается в стековый фрейм выше

anonymous
()
Ответ на: комментарий от drBatty

Это лишнее, если inline-функция должна использовать кучу временных переменных функции, из которой вызывается, а писать 100500 аргументов не хочется.

Если inline-функция ничего не возвращает, ее, конечно, проще всего вообще как макрос оформить (а чтобы знать, к чему этот макрос относится, удобно его определить прямо внутри той функции, из которой inline'ы вызываются).

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от annulen

> вложенные функции не входят в стандарт, это расширение GCC => kill it with fire!!!

+100500

mukoh
()
Ответ на: комментарий от Eddy_Em

Да, а если они в стандарт не входят, можно при помощи временных переменных и макросов их реализовать :)

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от anonymous

Самое забавное когда вложенная функция передается как аргумент. Она и в этом случае может дотягиваться до локальных переменных «основной» функции. Так как никакого «нормального» способа передать контекст в точку вызова в этом случае нет, то просто генерируется фрагмент кода для данного конкретного вызова, именно адрес оного кода (содержащий загрузку адреса стека основной функции) передается как аргумент. Работает, но изврат однако.

Система защиты ЛОРа достала.

io ★★
()
Ответ на: комментарий от Eddy_Em

>Да, а если они в стандарт не входят, можно при помощи временных переменных и макросов их реализовать :)

и усложнить себе-же жизнь :(

drBatty ★★
()
Ответ на: комментарий от drBatty

Можно писать замыкания, это чертовски удобно. Правда в Си не так удобно, слишком много всего писать, типы описывать надо. И коллеги могут не понять.

tensai_cirno ★★★★★
()
Ответ на: комментарий от io

enjoy your functional programming :-)

Вообще забавно, возьму на заметку.

tensai_cirno ★★★★★
()
Ответ на: комментарий от Eddy_Em

> Чуть мозг не закипел, пока пытался понять. Так и не понял.

Нормальный вопрос человек поднял, а то что вы его не поняли говорит о вашем низком уровне развития. Чтобы повысить его, почитайте про то как в языке Си организуются вызовы функций, тогда может быть поймете в чем проблема создания вложенных функций в языке Си.

anonymous
()
Ответ на: комментарий от Eddy_Em

>Чем? Наоборот, вместо кучи копипасты с незначительными различиями, имеем несколько строчек...

с этим я не спорю. вот только не все компиляторы такое поддерживают, ваш код будет непереносим. Я лучше займусь сейчас копипастой, чем потом буду переделывать. Благо современные редакторы (vim, emacs, даже kate) отлично копипастят.

drBatty ★★
()
Ответ на: комментарий от anonymous

Т.е. вы считаете, что это предложение написано на русском языке

как глобальные переменные не может быть из за многопоточности тогда выходит вложенная функция как то хитро дотягивается в стековый фрейм выше

?

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

> Т.е. вы считаете, что это предложение написано на русском языке

Согласен, что предложение у него написано криво, тем не менее общий смысл вопроса понять можно.

anonymous
()
Ответ на: комментарий от Eddy_Em

С пунктуацией беда просто.

А никто не смотрел, куда компилятор пихает разделяемые переменные? И как вложенная функция до них дотягивается? Как глобальные переменные — не может быть из-за многопоточности; тогда выходит вложенная функция как-то хитро дотягивается в стековый фрейм выше.

Fixed.

anonymous
()
Ответ на: комментарий от io

[quote]Самое забавное когда вложенная функция передается как аргумент. Она и в этом случае может дотягиваться до локальных переменных «основной» функции. Так как никакого «нормального» способа передать контекст в точку вызова в этом случае нет, то просто генерируется фрагмент кода для данного конкретного вызова, именно адрес оного кода (содержащий загрузку адреса стека основной функции) передается как аргумент.[/quote]

Proof в студию. 3 года назад gcc никаких лексических замыканий не делал. С трудом верится, что сейчас что-то подобное есть.

anonymous
()
Ответ на: комментарий от anonymous

3 года? Ну-Ну.

С примером нет проблем.
Вот не знаю как его перекосит LOR.

Дабы было не скучно использовал допотопную систему
gcc 3.2 (2002 год) и gdb (2001):

(gdb) l
1 void xxx(int (*)());
2
3 int bimbom()
4 {
5 int a = 0;
6 int tiptop()
7 {
8 a = 1;
9 }
10 xxx(tiptop);
(gdb)
11 return a;
12 }
13
14 main()
15 {
16 printf («%d\n», bimbom());
17 }
18
19 void xxx(int (*f)())
20 {
(gdb)
21 f();
22 }
23
24
(gdb) b bimbom
Breakpoint 1 at 0x4010d0: file proof.c, line 10.
(gdb) run
Starting program: proof

Breakpoint 1, bimbom () at proof.c:10
10 xxx(tiptop);
(gdb) s
5 int a = 0;
(gdb)
10 xxx(tiptop);
(gdb)
xxx (f=0x22fd90) at proof.c:21
21 f();
(gdb) si
0x0022fd90 in ?? ()
(gdb) disas
Этот код был сгенерирован в стеке, сейчас несколько иначе.
Dump of assembler code from 0x22fd90 to 0x22fdd0:
0x22fd90: mov $0x22fda0,%ecx
0x22fd95: jmp 0x401090 <tiptop.0>

Ассемблерный код:

_tiptop.0:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl %ecx, -4(%ebp)
movl $1, -4(%ecx)
movl %ebp, %esp
popl %ebp
ret
_bimbom:
pushl %ebp
movl %esp, %ebp
leal -24(%ebp), %eax
Генерация кода в стеке:
movl $_tiptop.0-10, %edx
leal -8(%ebp), %ecx
subl %eax, %edx
subl $40, %esp
movl %edx, 6(%eax)
movb $-71, (%eax)
movl %ecx, 1(%eax)
movb $-23, 5(%eax)
movl %eax, (%esp)
movl $0, -12(%ebp)
call _xxx
movl -12(%ebp), %eax
movl %ebp, %esp
popl %ebp
....

Рекомендую попробовать в новом - отличия будут, но принцип
trampoline сохранен.

io ★★
()
Ответ на: комментарий от anonymous

Здесь нет «генерации кода на стеке», больше похоже на обновление смещений в сегменте кода.

anonymous
()
Ответ на: комментарий от anonymous

Состояние памяти с будущим кодом в момент начала выполнения bimbom:

(gdb) disas 0x22fd90 0x22fd90+10
Dump of assembler code from 0x22fd90 to 0x22fd9a:
0x22fd90: add %al,(%eax)
0x22fd92: add %al,(%eax)
0x22fd94: add %al,(%eax)
0x22fd96: add %al,(%eax)
0x22fd98: test $0xfd,%al
End of assembler dump.

(gdb) disas
Dump of assembler code for function bimbom:
0x4010b0 <bimbom>: push %ebp
0x4010b1 <bimbom+1>: mov %esp,%ebp
0x4010b3 <bimbom+3>: lea 0xffffffe8(%ebp),%eax
0x4010b6 <bimbom+6>: mov $0x401086,%edx
0x4010bb <bimbom+11>: lea 0xfffffff8(%ebp),%ecx
0x4010be <bimbom+14>: sub %eax,%edx
0x4010c0 <bimbom+16>: sub $0x28,%esp
0x4010c3 <bimbom+19>: mov %edx,0x6(%eax)
0x4010c6 <bimbom+22>: movb $0xb9,(%eax)
0x4010c9 <bimbom+25>: mov %ecx,0x1(%eax)
0x4010cc <bimbom+28>: movb $0xe9,0x5(%eax)
0x4010d0 <bimbom+32>: mov %eax,(%esp,1)
0x4010d3 <bimbom+35>: movl $0x0,0xfffffff4(%ebp)
0x4010da <bimbom+42>: call 0x401120 <xxx>
0x4010df <bimbom+47>: mov 0xfffffff4(%ebp),%eax
0x4010e2 <bimbom+50>: mov %ebp,%esp
0x4010e4 <bimbom+52>: pop %ebp
0x4010e5 <bimbom+53>: ret
End of assembler dump.
(gdb) b *0x4010da
Breakpoint 2 at 0x4010da: file proof.c, line 10.
(gdb) c
Continuing.

Breakpoint 2, bimbom () at proof.c:10
10 xxx(tiptop);

Состояние той же памяти перед вызовом xxx:

(gdb) disas 0x22fd90 0x22fd90+10
Dump of assembler code from 0x22fd90 to 0x22fd9a:
0x22fd90: mov $0x22fda0,%ecx <<< обеcпечение доступа
0x22fd95: jmp 0x401090 <tiptop.0>
End of assembler dump.

Можно найти байты команд, которые генерируются выше (0xb9, 0xe9):

(gdb) x/16bx 0x22fd90
0x22fd90: 0xb9 0xa0 0xfd 0x22 0x00 0xe9 0xf6 0x12
0x22fd98: 0x1d 0x00 0x22 0x00 0x00 0x00 0x00 0x00
(gdb) p $sp
$3 = (void *) 0x22fd80

io ★★
()
Ответ на: комментарий от ky-san

> Как это будет работать с неисполняемым стеком?

А он в таком случае будет исполняемым. gcc запишет в ELF Program Header, что стэк должен быть исполняемым, и ядро его правильно мэпнет.

eXire ★★
()
Ответ на: комментарий от eXire

> > Как это будет работать с неисполняемым стеком?

А он в таком случае будет исполняемым.

Это уже неисполняемый стэк. Вопрос был чисто риторический

ky-san
()
Ответ на: комментарий от eXire

>А он в таком случае будет исполняемым. gcc запишет в ELF Program Header, что стэк должен быть исполняемым, и ядро его правильно мэпнет.

хороший бэкдор. Возьму на заметку. Спасибо.

drBatty ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.