Вызов по имени с динамическим охватом

Я застрял в следующей проблеме со статической/динамической областью видимости:

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

 global int i = 100, j = 5; 
 void P(x) { 
  int i = 10; 
  print(x + 10); 
  i = 200; 
  j = 20; 
  print (x); 
 } 
 main() {P(i + j);} 

Q1. Если язык программирования использует статическую область видимости и вызывает механизм передачи необходимых параметров, значения, напечатанные вышеприведенной программой, будут

(A) 115, 220 (B) 25, 220 (C) 25, 15 (D) 115, 105

Q2. Если язык программирования использует динамическую область видимости и механизм передачи параметров вызова по имени, значения, напечатанные вышеприведенной программой, будут

(A) 115, 220 (B) 25, 220 (C) 25, 15 (D) 115, 105

Что я думаю:

В вопросе 1: поскольку это статическая область видимости и в соответствии с вызовом по необходимости, x следует заменить на i + j. Но это вызовет локальный конфликт имен, так как уже существует переменная с именем i. Так что его (глобальный i) можно переименовать, скажем, в i1, и тогда вызов будет таким:

   first call: print(x+10) -> (i1 + j + 10) -> (100 + 5 + 10) -> 115
   second call: print(x) -> print(i1 + j) -> 105 (Already evaluated - call by need)

В вопросе 2: при динамической области видимости вы сначала ищете переменную в локальной функции, затем ищете в функции, вызвавшей локальную функцию, затем ищете в функции, вызвавшей эту функцию, и так далее вверх по стеку вызовов.

По вызову по имени:

print (i1 + j + 10) -> print (100 + 5 +10 ) -> 115

И второй звонок будет

print(x) -> print(i1 + j) -> (100 + 20) = 120 // Evaluate again - Call be name.

Этот ответ правильный? (Нет в опциях) Я что-то упустил? (Динамическое связывание может быть?)


person Pale Blue Dot    schedule 06.02.2013    source источник
comment
Вы это поняли?? Я застрял в той же проблеме!   -  person Mohit Jain    schedule 31.01.2014


Ответы (3)


Q1

Ответ ОП правильный (D). На самом деле, поскольку глобальный i не изменяется во время выполнения P, нет никакой разницы между вызовом по необходимости и вызовом по значению.

Вот пример, когда это действительно имеет значение:

global int i = 100, j = 5;

void IncreaseTheGlobal() {
    i = i + 1;            // static scoping means this is the GLOBAL i!
    print(i);
}

void P(x) {
    int i = 10;
    IncreaseTheGlobal();  // 101 (increased global i)
    print(i);             //  10 (local i)
    print(x);             // 106 (x is evaluated; picks up increased global i)
    IncreaseTheGlobal();  // 102 (2nd time increased global i)
    print(x);             // 106 (x not re-evaluated; unaffected by 2nd increase)
}

main() {
    print(i);             // 100 (original global i)
    P(i + j);
    print(i);             // 102 (new global i)
}

Как уже указывал OP, при первой оценке x он получает любое значение, которое глобальное i имеет в данный конкретный момент. После этой первоначальной оценки x больше не будет зависеть от более поздней модификации глобального i.

Q2

Вызов по имени обычно используется в макроязыках. Так почему бы не использовать самый известный язык макросов: препроцессор C?

#include <stdio.h>

int i = 100, j = 5;

#define print(num)  printf("%d\n", num)

#define P(x) {     \
    int i = 10;    \
    print(x + 10); \
    i = 200;       \
    j = 20;        \
    print(x);      \
}

main() {
    P(i + j);
}

Скомпилируйте, запустите и посмотрите: 25, 220.

Вызов по имени работает с простым поиском и заменой; внутри тела P замените каждое вхождение x на i + j.

int i = 10; 
print(i + j + 10);    // 10 + 5 + 10 = 25
i = 200;
j = 20;
print(i + j);         // 200 + 20 = 220

Другими словами, i и j внутри i + j просто получают текущую стоимость того, что оказывается в области видимости, когда оценивается x.

Значит, правильный ответ Б, верно? Ну, почти... правильный ответ зависит от реализации print. Предположим, что print также практикует вызов по имени, и print определяет свою собственную локальную переменную i, тогда результат резко изменится. Попробуй это:

#define print(num)  { int i = 0; printf("%d\n", num); }

Теперь результат изменится на 15, 20.

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

person Ruud Helderman    schedule 02.02.2014
comment
Извините, я ошибочно предположил, что Q1 был о колле по стоимости; Я внес изменения, чтобы мой ответ охватывал тему вызов по необходимости. - person Ruud Helderman; 03.02.2014
comment
Отличное объяснение!! Я голосую за это так сильно, как только могу!! - person Mohit Jain; 03.02.2014

Во второй части это Позови по имени

Строка i=200 обновит локальный i

Теперь при вызове print(x) он будет заменен на print(i+j)=>print(200+20)=>220

person user2044593    schedule 08.02.2013

Для Q1:

int i = 10; 
print(x + 10); // print (i + j + 10); prints 10 + 5 + 10 = 25; local i gets used here

i = 200; 
j = 20; 

print (x); // print (i + j); call by need ensures, no reevaluation and i + j is 15. 

Итак, ответ С - 25, 15

person Arjun Suresh    schedule 22.04.2015
comment
Возможно, это верно для динамической области видимости, но Q1 включает статическую область видимости. Несмотря на то, что x оценивается в рамках функции P, i + j оценивается в пределах (лексической) области, где находится i + j, то есть функции main. Попробуйте в Haskell: let { i=100; p x = let i=10 in x+10; main = p (i+5) } in main оценивается как 115, а не 25. - person Ruud Helderman; 29.05.2019