Можно ли использовать оператор постинкремента в параметрах вызова функции? в С? - PullRequest
3 голосов
/ 07 ноября 2019

Мой вопрос относится к вызовам функций в целом, но я думал об этом, когда писал приоритетную очередь с использованием кучи. Просто чтобы дать некоторый контекст (не то, чтобы это имело большое значение), моя куча хранит элементы сверху вниз слева направо, и я представляю кучу как массив структур. Вставив новый элемент, я просто помещаю его в последнее место в куче, а затем вызываю функцию «fix_up» внизу, которая переместит элемент в нужное место в куче. Мне интересно, если бы вместо того, чтобы делать ...

fix_up(pQueue->heap, pQueue->size);
pQueue->size++;

... Я мог бы просто сделать ...

fix_up(pQueue->heap, pQueue->size++);

Я не уверен, что это нормально для некоторыхпричины.
1) Поскольку pQueue-> size находится в вызове функции, я даже не уверен, является ли он на самом деле pQueue-> size или, скорее, копией целого числа, сохраненного в pQueue-> size. Если бы это была копия, то, очевидно, я бы не добавил 1 к фактическому pQueue-> size, поэтому не было бы никакого смысла делать это.

2) Поскольку это вызов функции, он затем перейдет в функцию fix_up и выполнит там весь код. Мне интересно, будет ли это иметь непреднамеренные последствия, может быть, когда он перейдет к fix_up, он будет увеличен на 1, и мой индекс будет на 1 выше, чем я предполагал при выполнении fix_up? Или он будет делать то, что должен делать, и ждать, пока не завершится выполнение fix_up?

3) Даже если все в порядке, считается ли это хорошей практикой кодирования для C?

Status priority_queue_insert(PRIORITY_QUEUE hQueue, int priority_level, int data_item)
{
    Priority_queue *pQueue = (Priority_queue*)hQueue;
    Item *temp_heap;
    int i;

    /*Resize if necessary*/
    if (pQueue->size >= pQueue->capacity) {
        temp_heap = (Item*)malloc(sizeof(Item) * pQueue->capacity * 2);
        if (temp_heap == NULL)
            return FAILURE;
        for (i = 0; i < pQueue->size; i++)
            temp_heap[i] = pQueue->heap[i];
        pQueue->capacity *= 2;
    }

    /*Either resizing was not necessary or it successfully resized*/
    pQueue->heap[pQueue->size].key = priority_level;
    pQueue->heap[pQueue->size].data = data_item;

    /*Now it is placed as the last item in the heap. Fixup as necessary*/
    fix_up(pQueue->heap, pQueue->size);
    pQueue->size++;

    //continue writing function code here
}

Ответы [ 4 ]

2 голосов
/ 07 ноября 2019

Хотя другие посты уже отвечают на этот вопрос, но ни один из них не говорит о роли Точки последовательности , в данном конкретном случае, которая может очень помочь в прояснении сомнений ОП.

С это [выделение шахты] :

Существует точка последовательности после оценки всех аргументов функции и функциии до фактического вызова функции .

С это [выделено мной] :

Операторы приращения инициируют побочный эффект добавления значения 1 соответствующего типа к операнду. Операторы декремента инициируют побочный эффект вычитания значения 1 соответствующего типа из операнда. Как и с любыми другими побочными эффектами, эти операции завершаются в или перед следующей точкой последовательности .

Кроме того, оператор постинкрементного увеличения значения операнда увеличивается на 1 но значение выражения является исходным значением операнда до операции приращения.

Итак, в этом утверждении:

fix_up(pQueue->heap, pQueue->size++);

значение pQueue->size будет увеличенона 1 до вызова функции fix_up(), но значение аргумента будет исходным значением до операции приращения.

2 голосов
/ 07 ноября 2019

Да, вы можете.

Однако вы не можете сделать это:

foo(myStruct->size++, myStruct->size)

Причина в том, что стандарт C не говорит, в каком порядке должны оцениваться аргументы. Это может привести к неопределенному поведению.

1) Поскольку pQueue-> size находится в вызове функции, я даже не уверен, является ли он на самом деле pQueue-> size или, скорее, копией сохраненного целого числав pQueue-> размер. Если бы это была копия, то, очевидно, я бы не добавил 1 к фактическому pQueue-> size, поэтому не было бы никакого смысла делать это.

Какой бы аргумент вы ни отправляли функции, он будет оценен до того, как функция начнет выполняться. Так что

T var = expr;
foo(var);

всегда эквивалентно

foo(expr);

2) Поскольку это вызов функции, он собирается затем перейти в функцию fix_up и выполнить весь код там,Мне интересно, будет ли это иметь непреднамеренные последствия, может быть, когда он перейдет к fix_up, он будет увеличен на 1, и мой индекс будет на 1 выше, чем я предполагал при выполнении fix_up? Или он будет делать то, что должен делать, и ждать, пока не завершится выполнение fix_up?

См. Выше

3) Даже если это нормально, считается ли это хорошей практикой кодирования для C?

В некоторой степени субъективно,и немного ОТ для этого сайта, но я все равно отвечу на это с моей личной точки зрения. В общем, я бы постарался этого избежать.

2 голосов
/ 07 ноября 2019

Да, вы можете использовать его непосредственно в выражении, которое вы передаете в качестве аргумента.

Выражение типа

fix_up(pQueue->heap, pQueue->size++);

в некоторой степени эквивалентно

{
    int old_value = pQueue->size;
    pQueue->size = pQueue->size + 1;
    fix_up(pQueue->heap, old_value);
}

Примечание о "эквивалентном" примере выше. Поскольку порядок вычисления аргументов для вызовов функций не указан, фактическое приращение pQueue->size может произойти после вызова fix_up. И это также означает, что использование pQueue->size более одного раза в одном вызове приведет к неопределенному поведению .

1 голос
/ 07 ноября 2019

Да, вы можете использовать его в вызовах функций, но учтите, что ваши два примера не эквивалентны. Аргумент pQueue->heap может оцениваться до или после pQueue->size++, и вы не можете знать или полагаться на порядок. Рассмотрите этот пример:

int func (void)
{
  static int x = 0;
  x++;
  return x;
}

printf("%d %d", func(), func());

Это напечатает 1 2 или 2 1, и мы не можем знать, что мы получим. Компилятору не нужно последовательно оценивать параметры функции по всей программе. Поэтому, если мы добавим вторую printf("%d %d", func(), func());, мы можем получить что-то вроде 1 2 4 3 в качестве вывода.

Здесь важно не писать код, который зависит от порядка вычисления. По той же причине, что и смешивание ++ с другими операциями или побочными эффектами в одном выражении, это плохая практика. В некоторых случаях это может даже привести к неопределенному поведению.

Чтобы ответить на ваши вопросы:

1) Поскольку pQueue-> size находится в вызове функции, я даже не уверенесли это на самом деле pQueue-> size или, вернее, копия целого числа, хранящегося в pQueue-> size. Если бы это была копия, то, очевидно, я бы не добавил 1 к фактическому pQueue-> size, поэтому в этом не было бы смысла.

++ применяется к переменной взвонящий, так что это не проблема. Локальная копия переменной происходит во время вызова функции независимо от ++. Однако результатом операции ++ не является так называемое lvalue (адресуемые данные), поэтому этот код недопустим:

void func (int* a);
...
func(&x++);  

++ имеет приоритет и оценивается первым. Результат не является lvalue, и его адрес не может быть получен.


2) Поскольку это вызов функции, он затем перейдет в функцию fix_up и выполнит там весь код. Мне интересно, будет ли это иметь непреднамеренные последствия, может быть, когда он перейдет к fix_up, он будет увеличен на 1, и мой индекс будет на 1 выше, чем я предполагал при выполнении fix_up? Или он будет делать то, что должен делать, и ждать, пока не завершится выполнение fix_up?

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

int* ptr;

void func (int a)
{
  *ptr = 1;
}

int x=5;
ptr = &x;
func(x++);

Это очень сомнительный код, и x будет 1 после строки func(x++);, а не 6, как можно было ожидать. Это связано с тем, что выражение вызова функции вычисляется и завершается перед вызовом функции.


3) Даже если это нормально, считается ли это хорошей практикой кодирования для C?

В вашем случае все будет хорошо, но это плохая практика. В частности, смешивание операторов ++ или -- вместе с другими операторами в одном и том же выражении является плохой (хотя и распространенной) практикой, поскольку она имеет большой потенциал для ошибок и имеет тенденцию делать код менее читаемым.

Ваш оригинальный код с pQueue->size++; в отдельной строке превосходит все - придерживайтесь этого. Вопреки распространенному мнению, при написании C вы не получаете бонусных баллов за «большинство операторов на одной линии». Однако вы можете получить ошибки и проблемы с обслуживанием.

...