Я просто наткнулся на заявление в вложенной с (dsPIC33)Многократное назначение в одной строке
sample1 = sample2 = 0;
это означало бы
sample1 = 0;
sample2 = 0;
Почему они печатают это так? Это хорошее или плохое кодирование?
Я просто наткнулся на заявление в вложенной с (dsPIC33)Многократное назначение в одной строке
sample1 = sample2 = 0;
это означало бы
sample1 = 0;
sample2 = 0;
Почему они печатают это так? Это хорошее или плохое кодирование?
Помните, что задание выполняется справа налево, и что они нормальные выражения. Таким образом, с точки зрения составителей линия
sample1 = sample2 = 0;
такая же, как
sample1 = (sample2 = 0);
, который так же, как
sample2 = 0;
sample1 = sample2;
То есть, sample2
присваивается ноль, то sample1
присваивается значение sample2
. На практике это то же самое, что присваивать оба значения нулю, как вы догадались.
sample1 = sample2 = 0;
действительно означает
sample1 = 0;
sample2 = 0;
тогда и только тогда, когда sample2
объявлена ранее.
Вы не можете сделать так:
int sample1 = sample2 = 0; //sample1 must be declared before assigning 0 to it
@ben; Ты не понимал, что я пытаюсь сказать. – haccks
Результаты те же. Некоторые люди предпочитают назначение цепочки, если все они имеют одинаковое значение. В этом подходе нет ничего плохого. Лично я считаю это предпочтительным, если переменные имеют близкие значения.
Что касается стиля кодирования и различные рекомендации кодирования смотрите здесь: Readability a=b=c or a=c; b=c;?
Я считаю, что с помощью
sample1 = sample2 = 0;
некоторые компиляторы будут немного быстрее по сравнению производить сборку 2 задания:
sample1 = 0;
sample2 = 0;
специально, если вы инициализируете ненулевое значение. Потому что множественное назначение переводится в:
sample2 = 0;
sample1 = sample2;
Итак, вместо 2 инициализаций вы делаете только одну и одну копию. Ускорение (если есть) будет крошечным, но во встроенном случае каждый крошечный бит подсчитывается!
Я сомневаюсь, что когда-либо существовал компилятор, который генерировал другой машинный код для 'sample2 = 0; sample1 = sample2; 'и' sample1 = sample2 = 0; ' – Lundin
@Lundin Я сравнивал множественное присваивание sampl1 = sample2 = 0; против двух одиночных: sample1 = 0; sample2 = 0; Тогда, я верю, есть разница, что вы думаете? –
Я сомневаюсь, что будет разница. Если есть, то 'sample1 = 0; sample2 = 0; '- более быстрая версия (на 1 тик процессора или так ...). – Lundin
Вы сами можете решить, что этот способ кодирования является хорошим или плохим.
Просто ознакомьтесь с кодом сборки для следующих линий в вашей среде IDE.
Затем измените код на два отдельных назначения и увидите различия.
В дополнение к этому, вы также можете попробовать отключить/на оптимизацию (оба размера & Оптимизация скорости) в вашем компиляторе, чтобы увидеть, как это влияет на код сборки.
Как и другие, порядок, в котором это выполняется, является детерминированным. приоритет оператора оператора = гарантирует, что это выполняется справа налево. Другими словами, он гарантирует, что 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.
Функциональные вызовы - это всего лишь один вид побочного эффекта, который может вызвать проблемы. летучие переменные, например аппаратные регистры, будут иметь такую же проблему, как описано здесь. – Lundin
Компилятор не может генерировать три вызова функции func() для 'sample1 = func() + (sample2 = func());'. –
@DKrueger Спасибо, это была опечатка на самом деле. Исправлена. – Lundin
Формально, для двух переменных 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, в которой результат присваивания в r-значении. В присваивании C++ вычисляется значение lvalue, которое требует секвенсирования «цепочек».)
Невозможно скажем, является ли это хорошей или плохой практикой программирования, не видя больше контекста. В случаях, когда две переменные тесно связаны (например, x
и y
координата точки), установив их на некоторое общее значение, используя «прикованное» назначение, на самом деле является отличной практикой (я бы даже сказал «рекомендуемая практика»). Но когда переменные полностью не связаны друг с другом, смешение их в одном «прикованном» задании, безусловно, не очень хорошая идея. Особенно, если эти переменные имеют разные типы, что может привести к непреднамеренным последствиям.
Я полагаю, что стандарт указывает, что эффект * не *, как будто переменная была написана сперва - это различие, которое может быть чрезвычайно важным, если рассматриваемая переменная 'volatile'. – supercat
Я думаю, что нет хорошего ответа на языке C без фактической сборки листинга :)
Так что для упрощенной программы:
int main() {
int a, b, c, d;
a = b = c = d = 0;
return a;
}
Я получил эту assemly (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
Так НКУ является фактически цепочкой всех вещей.
Компилятор может свободно выполнять любое преобразование кода, которое не изменяет наблюдаемое поведение. Поэтому сборка ничего не доказывает вообще. Единственное, на что вы можете положиться, это стандартная спецификация C++ (если компилятор обещает подчиняться стандарту). – Markus
Возможно, стоит отметить, что в этом выражении нет последовательности. Это означает, что неверно предположить, что сначала задан 'sample2', а значение, присвоенное' sample1', буквально считывается из 'sample2'. Вернее, 'sample1' присваивается значение' 0', преобразованное в тип 'sample2'. И назначение 'sample1' может произойти до назначения на' sample2'. – AnT
Спасибо за ответ, я бы избегал sample1 = sample2 = 0 во имя читаемости и последовательности, как указано в AndreyT. – riscy
К этому я бы добавил, что 'sample2 = 0' возвращает значение rvalue, равное значению, заданному в' sample2'. Таким образом, это оператор присваивания и дополнительно имеет возвращаемое значение, равное его назначению. Это возвращаемое значение (rvalue) - это то, что присваивается 'sample1'. – Hazem