2013-10-14 3 views

ответ

25

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

sample1 = sample2 = 0; 

такая же, как

sample1 = (sample2 = 0); 

, который так же, как

sample2 = 0; 
sample1 = sample2; 

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

+6

Возможно, стоит отметить, что в этом выражении нет последовательности. Это означает, что неверно предположить, что сначала задан 'sample2', а значение, присвоенное' sample1', буквально считывается из 'sample2'. Вернее, 'sample1' присваивается значение' 0', преобразованное в тип 'sample2'. И назначение 'sample1' может произойти до назначения на' sample2'. – AnT

+0

Спасибо за ответ, я бы избегал sample1 = sample2 = 0 во имя читаемости и последовательности, как указано в AndreyT. – riscy

+3

К этому я бы добавил, что 'sample2 = 0' возвращает значение rvalue, равное значению, заданному в' sample2'. Таким образом, это оператор присваивания и дополнительно имеет возвращаемое значение, равное его назначению. Это возвращаемое значение (rvalue) - это то, что присваивается 'sample1'. – Hazem

0
sample1 = sample2 = 0; 

действительно означает

sample1 = 0; 
sample2 = 0; 

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

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

@ben; Ты не понимал, что я пытаюсь сказать. – haccks

1

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

0

Что касается стиля кодирования и различные рекомендации кодирования смотрите здесь: Readability a=b=c or a=c; b=c;?

Я считаю, что с помощью

sample1 = sample2 = 0; 

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

sample1 = 0; 
sample2 = 0; 

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

sample2 = 0; 
sample1 = sample2; 

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

+0

Я сомневаюсь, что когда-либо существовал компилятор, который генерировал другой машинный код для 'sample2 = 0; sample1 = sample2; 'и' sample1 = sample2 = 0; ' – Lundin

+0

@Lundin Я сравнивал множественное присваивание sampl1 = sample2 = 0; против двух одиночных: sample1 = 0; sample2 = 0; Тогда, я верю, есть разница, что вы думаете? –

+0

Я сомневаюсь, что будет разница. Если есть, то 'sample1 = 0; sample2 = 0; '- более быстрая версия (на 1 тик процессора или так ...). – Lundin

1

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

  1. Просто ознакомьтесь с кодом сборки для следующих линий в вашей среде IDE.

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

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

0

Как и другие, порядок, в котором это выполняется, является детерминированным. приоритет оператора оператора = гарантирует, что это выполняется справа налево. Другими словами, он гарантирует, что 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.

+0

Функциональные вызовы - это всего лишь один вид побочного эффекта, который может вызвать проблемы. летучие переменные, например аппаратные регистры, будут иметь такую ​​же проблему, как описано здесь. – Lundin

+1

Компилятор не может генерировать три вызова функции func() для 'sample1 = func() + (sample2 = func());'. –

+0

@DKrueger Спасибо, это была опечатка на самом деле. Исправлена. – Lundin

8

Формально, для двух переменных 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 координата точки), установив их на некоторое общее значение, используя «прикованное» назначение, на самом деле является отличной практикой (я бы даже сказал «рекомендуемая практика»). Но когда переменные полностью не связаны друг с другом, смешение их в одном «прикованном» задании, безусловно, не очень хорошая идея. Особенно, если эти переменные имеют разные типы, что может привести к непреднамеренным последствиям.

+0

Я полагаю, что стандарт указывает, что эффект * не *, как будто переменная была написана сперва - это различие, которое может быть чрезвычайно важным, если рассматриваемая переменная 'volatile'. – supercat

5

Я думаю, что нет хорошего ответа на языке 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 

Так НКУ является фактически цепочкой всех вещей.

+3

Компилятор может свободно выполнять любое преобразование кода, которое не изменяет наблюдаемое поведение. Поэтому сборка ничего не доказывает вообще. Единственное, на что вы можете положиться, это стандартная спецификация C++ (если компилятор обещает подчиняться стандарту). – Markus

Смежные вопросы