Преобразуют ли основные компиляторы базовые типы, переданные по ссылке, в копию за копией? - PullRequest
7 голосов
/ 31 октября 2011

Передача объекта по ссылке - это более простой, быстрый и безопасный способ передать ему адрес.Но для большинства компиляторов все одинаково: ссылки - это действительно указатели.

А как насчет базовых типов, таких как int?Передача адреса в int и использование его внутри функции будет медленнее, чем передача его путем копирования, поскольку перед использованием необходимо разыменовать указатель.

Как современный компилятор обрабатывает это?

int foo(const int & i)
{
   cout << i; // Do whatever read-only with i.
}

Могу ли я доверять им, чтобы скомпилировать это в это?

int foo(const int i)
{
   cout << i;
}

Кстати, в некоторых случаях это может быть даже быстрее, чтобы передать и i, и &i, затем использоватьi для чтения и *i для записи.

int foo(const int i, int * ptr_i)
{
   cout << i;    // no dereferencement, therefore faster (?)
   // many more read-only operations with i.
   *ptr_i = 123;
}

Ответы [ 4 ]

5 голосов
/ 31 октября 2011

Могу ли я доверять им скомпилировать это в это?
Да Вы можете. [Да здесь означает по-другому, пожалуйста, прочитайте раздел Правка, который разъясняет]

int foo(const int & i)

Сообщает компилятору, что i является ссылкой на константу типа integer.
Компилятор может выполнять оптимизацию, но ему разрешено выполнять оптимизации только в соответствии с As-If Rule .Таким образом, вы можете быть уверены, что для вашей программы поведение выше будет таким же, как (квалификатор const будет соблюдаться):

int foo(const int i)

Правило «как если»:

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

Для вентиляторов Standerdese:
C ++ 03 1.9 "Выполнение программы:

, соответствующие реализации необходимы для эмуляции (только)наблюдаемое поведение абстрактной машины.

И в Foot-Note говорится:

Это положение иногда называют правилом «как будто», потому что реализация свободнаигнорировать любое требование настоящего международного стандарта при условии, что в результате будет выполнено требование, насколько это можно определить из наблюдаемого поведения программы. Например, фактическая реализация не должна оценивать часть выражения, еслион может сделать вывод, что его значение не используется и что не возникает никаких побочных эффектов, влияющих на наблюдаемое поведение программы.

РЕДАКТИРОВАТЬ:
Поскольку в ответе есть некоторая путаница, позвольте мне уточнить:
Оптимизация не может быть применена к компилятору. Так как компилятор интерпретирует это, зависит от компилятора. Важный тонкийg - наблюдаемое поведение программы не изменится.

4 голосов
/ 01 ноября 2011

Это не должно компилировать это, потому что это может быть неправильно. Рассмотрим:

int foo(const int &i, int *p)
{
   *p = 42;
   cout << i; // prints 42
   return 0;
}

int main()
{
   int x = 5;
   foo(x, &x);
   return 0;
}

против

int foo(const int i, int *p)
{
   *p = 42;
   cout << i; // prints 5
   return 0;
}

int main()
{
   int x = 5;
   foo(x, &x);
   return 0;
}

Как компилятор знает, что этого не произойдет? Нужно было бы каким-то образом проанализировать, что получить доступ к этой переменной невозможно, например, изменить ее. (1) кто-то, имеющий указатель, (2) это может быть глобальная переменная, (3) из другого потока. Учитывая небезопасную природу C, с арифметикой указателей и прочим, даже гарантировать, что функция не сможет получить указатель на переменную, может быть невозможно.

0 голосов
/ 12 августа 2013

gcc не выполняет эту оптимизацию с -O3 (gcc версия 4.7.2). Используя код Габриэля, обратите внимание, как ok2 загружает разыменованный адрес перед индексацией в vars, а ok1 - нет.

ok1:

<code>
    .cfi_startproc
    subq    $40, %rsp
    .cfi_def_cfa_offset 48
    movslq  %edi, %rdi
    fildl   vars(,%rdi,4)
    fld %st(0)
    fsqrt
    fucomi  %st(0), %st
    jp  .L7
    fstp    %st(1)
OK2:
<code>
    .cfi_startproc
    subq    $40, %rsp
    .cfi_def_cfa_offset 48
    movslq  (%rdi), %rax
    fildl   vars(,%rax,4)
    fld %st(0)
    fsqrt
    fucomi  %st(0), %st
    jp  .L12
    fstp    %st(1)
0 голосов
/ 02 ноября 2011

Visual Studio 2010 (Express) работает, по крайней мере, в простых случаях, которые я тестировал. Кто-нибудь, чтобы проверить gcc?

Я проверял следующее:

1. Проезжает только i:

int vars[] = {1,2,3,12,3,23,1,213,231,1,21,12,213,21321,213,123213,213123};

int ok1(const int i){
    return sqrtl(vars[i]);
}

int ok2(const int & i){
    return sqrtl(vars[i]);
}

void main() {
    int i;
    std::cin >> i;
    //i = ok1(i);
    i = ok2(i);
    std::cout << i;
}

ASM:

i = ok1(i);
000D1014  mov         ecx,dword ptr [i]  
000D1017  fild        dword ptr vars (0D3018h)[ecx*4]  
000D101E  call        _CIsqrt (0D1830h)  
000D1023  call        _ftol2_sse (0D1840h) 

i = ok2(i);
013A1014  mov         ecx,dword ptr [i]  
013A1017  fild        dword ptr vars (13A3018h)[ecx*4]  
013A101E  call        _CIsqrt (13A1830h)  
013A1023  call        _ftol2_sse (13A1840h)

Что ж, ASM идентичны, без сомнения, была проведена оптимизация.

2. Проходя i и &i:

Давайте рассмотрим ансер @newacct здесь.

int vars[] = {1,2,3,12,3,23,1,213,231,1,21,12,213,21321,213,123213,213123};

int ok1(const int i, int * pi) {
    *pi = 2;
    return sqrtl(vars[i]);
}

int ok2(const int & i, int * pi) {
    *pi = 2;
    return sqrtl(vars[i]);
}

void main() {
    int i;
    int * pi = &i;
    std::cin >> i;
    i = ok1(i, pi);
    //i = ok2(i, pi);
    std::cout << i;
}

ASM:

i = ok1(i, pi);
00891014  mov         ecx,dword ptr [i]
00891017  fild        dword ptr vars (893018h)[ecx*4] // access vars[i] 
0089101E  call        _CIsqrt (891830h)  
00891023  call        _ftol2_sse (891840h)  

i = ok2(i, pi);
011B1014  fild        dword ptr [vars+8 (11B3020h)]   // access vars[2]
011B101A  call        _CIsqrt (11B1830h)  
011B101F  call        _ftol2_sse (11B1840h) 

В ok1 Я не вижу записи 2 * в pi. Вероятно, он понимает, что область памяти в любом случае будет перезаписана результатом функции, поэтому запись бесполезна.

С ok2 компилятор настолько умный, насколько я ожидал. Он понимает, что i и pi указывают на одно и то же место, поэтому он использует жестко 2 напрямую.

Примечания:

  • Я скомпилировал два раза для обоих тестов, один раз раскомментировав только ok1, один раз раскомментировав только ok2. Компиляция обоих одновременно приводит к более сложной оптимизации между двумя функциями, которые в конечном итоге все встраиваются и смешиваются
  • Я добавил поиск в массиве vars, потому что простые вызовы sqrtl были упрощены до базовых ADD- и MUL-подобных операций без фактического вызова
  • Скомпилировано в выпуске
  • Дали ожидаемые результаты, конечно же
...