Подтвердить что ты не робот

Множественное назначение в одной строке

Я просто встречаюсь с утверждением во встроенном c (dsPIC33)

sample1 = sample2 = 0;

Будет ли это означать

sample1 = 0;

sample2 = 0;

Почему они так печатают? Это хорошее или плохое кодирование?

4b9b3361

Ответ 1

Помните, что назначение выполняется справа налево и что они являются нормальными выражениями. Таким образом, с точки зрения компиляторов строка

sample1 = sample2 = 0;

совпадает с

sample1 = (sample2 = 0);

что совпадает с

sample2 = 0;
sample1 = sample2;

То есть, sample2 назначается ноль, тогда sample1 присваивается значение sample2. На практике это то же самое, что присваивать оба значения нулю, как вы догадались.

Ответ 2

Формально для двух переменных t и u типа t и u соответственно

T t;
U u;

назначение

t = u = X;

(где X - некоторое значение) интерпретируется как

t = (u = X);

и эквивалентно паре независимых назначений

u = X;
t = (U) X;

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

t = (U) X;
u = X;

также является допустимым расписанием выполнения для этого выражения. (Обратите внимание, что эта свобода секвенирования специфична для языка C, в которой результат присваивания в rvalue. В присваивании С++ вычисляется значение lvalue, которое требует "привязанных" назначений для секвенирования.)

Невозможно сказать, является ли это хорошей или плохой практикой программирования, не видя больше контекста. В случаях, когда две переменные тесно связаны между собой (например, X и y координата точки), установка их в какое-то общее значение с использованием "привязанного" назначения на самом деле является прекрасной практикой (я бы даже сказал "рекомендуемая практика",). Но когда переменные полностью не связаны друг с другом, смешение их в одном "прикованном" задании, безусловно, не очень хорошая идея. Особенно, если эти переменные имеют разные типы, что может привести к непреднамеренным последствиям.

Ответ 3

Я думаю, что нет хорошего ответа на языке C без фактического списка сборок:)

Итак, для упрощенной программы:

int main() {
        int a, b, c, d;
        a = b = c = d = 0;
        return a;
}

У меня есть эта сборка (Kubuntu, gcc 4.8.2, x86_64) с опцией -O0, конечно;)

main:
        pushq   %rbp
        movq    %rsp, %rbp

        movl    $0, -16(%rbp)       ; d = 0
        movl    -16(%rbp), %eax     ; 

        movl    %eax, -12(%rbp)     ; c = d
        movl    -12(%rbp), %eax     ;

        movl    %eax, -8(%rbp)      ; b = c
        movl    -8(%rbp), %eax      ;

        movl    %eax, -4(%rbp)      ; a = b
        movl    -4(%rbp), %eax      ;

        popq    %rbp

        ret                         ; return %eax, ie. a

Итак, gcc на самом деле цепляет все.

Ответ 4

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

Ответ 5

Вы сами можете решить, что этот способ кодирования хорош или плох.

  • Просто посмотрите код сборки для следующих строк в вашей среде IDE.

  • Затем измените код на два отдельных назначения и увидите различия.

В дополнение к этому вы также можете попытаться отключить/включить оптимизацию (как размер, так и оптимизацию скорости) в своем компиляторе, чтобы увидеть, как это влияет на код сборки.

Ответ 6

sample1 = sample2 = 0;

означает

sample1 = 0;
sample2 = 0;  

тогда и только тогда, когда sample2 объявлен ранее.
Вы не можете сделать так:

int sample1 = sample2 = 0; //sample1 must be declared before assigning 0 to it

Ответ 7

Что касается стиля кодирования и различных рекомендаций по кодированию, см. здесь: Читаемость a = b = c или = c; B = C;?

Я верю, что, используя

sample1 = sample2 = 0;

некоторые компиляторы будут производить сборку немного быстрее по сравнению с двумя присваиваниями:

sample1 = 0;
sample2 = 0;

особенно если вы инициализируете ненулевое значение. Поскольку множественное присвоение означает:

sample2 = 0; 
sample1 = sample2;

Итак, вместо 2 инициализаций вы делаете только одну и одну копию. Скорость (если есть) будет крошечной, но во встроенном случае каждый крошечный бит будет считаться!

Ответ 8

Как говорили другие, порядок, в котором это выполняется, является детерминированным. Приоритет оператора оператора = 1 гарантирует, что это выполняется справа налево. Другими словами, он гарантирует, что sample2 получает значение до образца1.

Однако несколько присвоений в одной строке являются плохой практикой и запрещены многими стандартами кодирования (*). Прежде всего, это не особенно читаемо (или вы не задавали бы этот вопрос). Во-вторых, это опасно. Если мы имеем, например,

sample1 = func() + (sample2 = func());

тогда приоритет оператора гарантирует тот же порядок выполнения, что и раньше (+ имеет более высокий приоритет, чем =, поэтому скобка). sample2 получит значение перед sample1. Но в отличие от приоритета оператора порядок оценки операторов не является детерминированным, это неопределенное поведение. Мы не можем знать, что вызов самой правой функции оценивается до самого левого.

Компилятор может свободно переводить вышеуказанный код машины следующим образом:

int tmp1 = func();
int tmp2 = func();
sample2 = tmp2;
sample1 = tmp1 + tmp2;

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


(*) MISRA-C: 2004 12.2, MISRA-C: 2012 13.4, CERT-C EXP10-C.

Ответ 9

Позаботьтесь об этом частном случае... предположим, что b - массив структуры вида

{
    int foo;
}

и пусть я - смещение в b. Рассмотрим функцию realloc_b(), возвращающую int и выполняющую перераспределение массива b. Рассмотрим это множественное назначение:

a = (b + i) → foo = realloc_b();

К моему опыту (b + i) сначала решается, скажем, b_i в ОЗУ; то выполняется realloc_b(). Поскольку realloc_b() изменяет b в ОЗУ, это приводит к тому, что b_i больше не выделяется.

Переменная a хорошо назначена, но (b + i) → foo не потому, что b изменяется при выполнении самого левого члена присваивания, т.е. realloc_b()

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

Чтобы быть без ошибок и иметь (b + i) → foo, равный a, однострочное присвоение должно быть разделено на два назначения:

a = reallocB();
(b + i)->foo = a;