Я просто встречаюсь с утверждением во встроенном c (dsPIC33)
sample1 = sample2 = 0;
Будет ли это означать
sample1 = 0;
sample2 = 0;
Почему они так печатают? Это хорошее или плохое кодирование?
Я просто встречаюсь с утверждением во встроенном c (dsPIC33)
sample1 = sample2 = 0;
Будет ли это означать
sample1 = 0;
sample2 = 0;
Почему они так печатают? Это хорошее или плохое кодирование?
Помните, что назначение выполняется справа налево и что они являются нормальными выражениями. Таким образом, с точки зрения компиляторов строка
sample1 = sample2 = 0;
совпадает с
sample1 = (sample2 = 0);
что совпадает с
sample2 = 0;
sample1 = sample2;
То есть, sample2
назначается ноль, тогда sample1
присваивается значение sample2
. На практике это то же самое, что присваивать оба значения нулю, как вы догадались.
Формально для двух переменных 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
координата точки), установка их в какое-то общее значение с использованием "привязанного" назначения на самом деле является прекрасной практикой (я бы даже сказал "рекомендуемая практика",). Но когда переменные полностью не связаны друг с другом, смешение их в одном "прикованном" задании, безусловно, не очень хорошая идея. Особенно, если эти переменные имеют разные типы, что может привести к непреднамеренным последствиям.
Я думаю, что нет хорошего ответа на языке 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 на самом деле цепляет все.
Результаты те же. Некоторые люди предпочитают назначение цепочки, если все они имеют одинаковое значение. В этом подходе нет ничего плохого. Лично я считаю это предпочтительным, если переменные имеют близкие значения.
Вы сами можете решить, что этот способ кодирования хорош или плох.
Просто посмотрите код сборки для следующих строк в вашей среде IDE.
Затем измените код на два отдельных назначения и увидите различия.
В дополнение к этому вы также можете попытаться отключить/включить оптимизацию (как размер, так и оптимизацию скорости) в своем компиляторе, чтобы увидеть, как это влияет на код сборки.
sample1 = sample2 = 0;
означает
sample1 = 0;
sample2 = 0;
тогда и только тогда, когда sample2
объявлен ранее.
Вы не можете сделать так:
int sample1 = sample2 = 0; //sample1 must be declared before assigning 0 to it
Что касается стиля кодирования и различных рекомендаций по кодированию, см. здесь: Читаемость a = b = c или = c; B = C;?
Я верю, что, используя
sample1 = sample2 = 0;
некоторые компиляторы будут производить сборку немного быстрее по сравнению с двумя присваиваниями:
sample1 = 0;
sample2 = 0;
особенно если вы инициализируете ненулевое значение. Поскольку множественное присвоение означает:
sample2 = 0;
sample1 = sample2;
Итак, вместо 2 инициализаций вы делаете только одну и одну копию. Скорость (если есть) будет крошечной, но во встроенном случае каждый крошечный бит будет считаться!
Как говорили другие, порядок, в котором это выполняется, является детерминированным. Приоритет оператора оператора = 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.
Позаботьтесь об этом частном случае... предположим, что 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;