Как код ведет себя отличающийся для Java и компилятора C?

Я обычно пишу свой собственный класс ConvertDBNull, который обертывает встроенный класс Преобразования. Если значение будет DBNull, то это возвратит пустой указатель, если это будет ссылочный тип или значение по умолчанию, если это - тип значения. Пример: - ConvertDBNull.ToInt64(object obj) возвраты Convert.ToInt64(obj), если obj не является DBNull, в этом случае, это возвратится 0.

7
задан Bill the Lizard 18 September 2012 в 03:08
поделиться

6 ответов

Как оценивается термин? Правая часть - x - x - оценивается как 0 как для Java, так и для C, но меняет значение x . Итак, вопрос: как работает - = ? Считывает ли он x перед оценкой правой части (RHS) и затем вычитает RHS, или делает это после оценки RHS. Итак, у вас есть

tmp = x // copy the value of x
x = tmp - (--x - x--) // complicated way to say x = x

или

tmp = (--x - x--) // first evaluate RHS, from left to right, which means x -= 2.
x = x - tmp // substract 0 from x

. В Java есть правило:

Составное выражение присваивания в форме E1 op = E2 эквивалентно E1 = (T) ((E1) op (E2)) , где T - тип E1, за исключением того, что E1 вычисляется только один раз. (см. 15.26.2 Операторы составного присваивания )

Это означает, что значение копируется, поэтому пре- и пост-декременты не действуют. Ваш компилятор C, вероятно, использует другое правило.

Для C,

[EDIT] Паскаль Куок (см. Ниже) настаивает на том, что в стандарте указано, что результат не определен. Вероятно, это правильно: я смотрел на ту часть, которую он скопировал из стандарта, в течение нескольких минут и не мог понять, что говорится в этом предложении. Думаю, я здесь не один :) Итак, я пошел посмотреть, как работает интерпретатор C, который я разработал для моей магистерской диссертации. Это не соответствует стандартам, но я понимаю, как это работает. Угадайте, я парень типа Гейзенберга: я могу иметь любую точность с любой точностью, но не обе;) В любом случае.

При синтаксическом анализе этой конструкции вы получаете следующее дерево синтаксического анализа:

        +---- (-=) ----+
        v     -=       v
        x        +--- (-) ----+
                 v            v
              PREDEC x    POSTDEC x

Стандарт гласит, что изменение x трижды (один раз слева и дважды в двух операциях декремента) оставляет x неопределенным. Хорошо. Но компилятор - это детерминированная программа, поэтому, когда он принимает некоторый ввод, он всегда будет давать одинаковый результат. И большинство компиляторов работают одинаково. Я думаю, мы все согласны с тем, что любой компилятор C фактически принимает этот ввод. Какие результаты мы можем ожидать? Ответ: 6 или 8. Рассуждение:

  1. xx равно 0 для любого значения x.
  2. - xx равно 0 для любого значения x, поскольку его можно записать как - x, xx
  3. xx - равно 0 , потому что результат оператора минус вычисляется до постдекремента.

Итак, если пре-декремент не влияет на результат, и пост-декремент не влияет. Кроме того, нет никакого вывода между двумя операторами (использование их обоих в одном выражении, как в a = --y - x - , не меняет их поведения). Вывод: все и любой компилятор C вернет 0 для - x - x - (ну, кроме глючных).

Что оставляет нас с моим исходным предположением: value RHS не влияет на результат, он всегда принимает значение 0 , но изменяет x . Итак, вопрос в том, как - = реализовано? Здесь играет роль несколько факторов:

  1. Имеется ли в ЦП собственный оператор для - = ? ЦП на основе регистров (на самом деле, у них есть только такие операторы. Для выполнения a + b , они должны скопировать a в регистр, а затем они могут + = b к нему), ЦП на основе стека - нет (они помещают все значения в стек, а затем используют операторы, которые используют самые верхние элементы стека в качестве операндов).
  2. Значения сохраняются в стеке или в регистрах? (Другой способ задать первый вопрос)
  3. Какие параметры оптимизации активны?

Чтобы продолжить, мы должны посмотреть на код:

#include <stdio.h>

int main() {
        int x = 8;
        x -= --x - x--;
        printf("x=%d\n", x);
}

При компиляции мы получаем этот код ассемблера для назначения (код x86 ):

    .loc 1 4 0
    movl    $8, -4(%rbp)    ; x = 8
    .loc 1 5 0
    subl    $1, -4(%rbp)    ; x--
    movl    $0, %eax        ; tmp = 0
    subl    %eax, -4(%rbp)  ; x -= tmp
    subl    $1, -4(%rbp)    ; x--
    .loc 1 6 0
    movl    -4(%rbp), %esi  ; push `x` into the place where printf() expects it

Первый movl устанавливает x в 8 , что означает -4 (% rbp) равно x . Как вы можете видеть, компилятор фактически замечает xx и оптимизирует его до 0 , как и было предсказано (даже без каких-либо опций оптимизации). У нас также есть две ожидаемые операции - , что означает, что результат всегда должен быть 6 .

Итак, кто прав? Мы оба. Паскаль прав, когда говорит, что стандарт не определяет такое поведение. Но это не значит s случайный. Все части кода имеют четко определенное поведение, поэтому поведение суммы не может внезапно быть неопределенным (если не пропущено что-то еще - но не в этом случае). Так что, хотя стандарт не решает эту проблему, он все равно детерминирован.

Для ЦП на основе стека (у которых нет регистров) результат должен быть 8, поскольку они будут копировать значение x , прежде чем они начнут оценивать правую часть. Для процессоров на основе регистров всегда должно быть 6.

Моральный дух: стандарт всегда правильный, но если вы должны понять, посмотрите на код;)

Чтобы решить эту проблему, она все еще детерминирована.

Для ЦП на основе стека (у которых нет регистров) результат должен быть 8, поскольку они скопируют значение x , прежде чем они начнут оценивать справа. Для процессоров на основе регистров всегда должно быть 6.

Моральный дух: стандарт всегда правильный, но если вы должны понять, посмотрите на код;)

Чтобы решить эту проблему, она все еще детерминирована.

Для ЦП на основе стека (у которых нет регистров) результат должен быть 8, поскольку они скопируют значение x , прежде чем они начнут оценивать справа. Для процессоров на основе регистров всегда должно быть 6.

Моральный дух: стандарт всегда правильный, но если вы должны понять, посмотрите на код;)

5
ответ дан 6 December 2019 в 04:56
поделиться

В C ++ результат является неопределенным, т. Е. Не указан и не гарантированно согласован - компилятор может делать все, что ему больше всего подходит, в любое время на основе точек последовательности ].

Я подозреваю, что то же самое для Java [и C # и т. Д.]

4
ответ дан 6 December 2019 в 04:56
поделиться

Что ж ... что вы считаете правильным и каковы ваши рассуждения?

Я считаю, что x довольно хорошо определено для первых трех шагов

x = 10
x is decremented (its initial value is used first)
x is decremented again (its resulting value is used after)

Теперь x == 8 . Но посмотрите, что вы делаете с этим здесь (простите за вставку удобных для человека пробелов):

x -= --x - x--

Которая может быть скомпилирована (это то, что я сделал бы, если бы у меня было , чтобы включить Операторы ++ и - на моем языке - сначала выявляются побочные эффекты и удаляются в начале и в конце инструкции в целом):

--x
t = x - x
x -= t
x--

Выдача результата х == 8 . Или, может быть, он был скомпилирован в (оператор сокращается сначала подвыражением):

t1 = --x     // t1 = 7, x = 7
t2 = x--     // t2 = 7, x = 6
t = t1 - t2  // t = 7 - 7 = 0
x -= t       // x = 6

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

3
ответ дан 6 December 2019 в 04:56
поделиться

The fundamental difference between Java and C is that in C language the temporal relationships between different actions (what happens "before" and what happens "after") is determined by so called sequence points. Sequence points implement the concept of time in the process of execution of C program. If two actions are separated from each other by a sequence point, then you can say that one action happens "before" and another happens "after". When two actions have no sequence point between them, there's no defined temporal ordering between them and there's no way to say what happens "first" and what happens "later". Consider a pair of adjacent sequence points in C program as the minimal indivisible unit of time. What happens within that unit of time cannot be described in terms of "before" and "after". One might as well think that between two adjacent sequence points everything is happening simultaneously. Or in random order, whichever you prefer.

In C language the statement

x -= --x - x--;

has no sequence points inside. It only has a sequence point at the very beginning and at the very end. This means that there's no way to say in which order this expression statement is evaluated. It is indivisible it terms of C time, as described above. Every time someone tries to explain what happens here by imposing a specific temporal ordering, they are just wasting their time and producing utter nonsense. This is actually the reason why C language does not (and cannot) make any attempts to define the behavior of expressions with multiple modifications of the same object (x in the above example). The behavior is undefined.

Java is significantly different in this respect. In Java the concept of time is defined differently. In Java the expressions are always evaluated in strict order defined by the operator precedence and associativity. This imposes a strict temporal ordering on the events that take place during the evaluation of the above expression. This makes the result of this expression defined, as opposed to C.

3
ответ дан 6 December 2019 в 04:56
поделиться

Вы ошибаетесь, когда говорите, что вывод этого кода, рассматриваемого как программа C, равен 6 .

Рассматриваемая как программа C, это undefined . Вы просто случайно получили 6 с вашим компилятором, но с тем же успехом вы могли получить 24, ошибку сегментации или ошибку времени компиляции.

См. стандарт C99 , 6.5.2:

] Между предыдущей и следующей точкой последовательности у объекта должно быть сохраненное значение изменяется не более одного раза при вычислении выражения. Кроме того, предыдущее значение должны быть прочитаны только для определения значения, которое будет сохранено.71)

- xx - явно запрещено этим параграфом.

РЕДАКТИРОВАТЬ:

Аарон Дигулла пишет в комментариях:

Это действительно undefined?

Вы заметили, что я связался со стандартом C99 и указал параграф, в котором говорится, что это undefined?

gcc -Wall (GCC 4.1.2) не жалуется на это, и я сомневаюсь, что любой компилятор отклонил бы этот код.

Стандарт описывает некоторые варианты поведения как «неопределенные» именно потому, что не все способы, по которым программа на C является бессмысленной, могут быть надежно обнаружены во время компиляции. Если вы думаете, что "без предупреждения" должно означать, что все в порядке, вам следует переключиться на другой язык, кроме C. Многие современные языки определены лучше. Я использую OCaml , когда у меня есть выбор, но есть бесчисленное множество других четко определенных языков.

Есть причина, по которой он возвращает 6, и вы должны быть в состоянии объяснить это.

Я не заметил вашего объяснения того, почему это выражение оценивается как 6. Надеюсь, вы не тратьте слишком много времени на его написание, потому что для меня он возвращает 0.

Macbook:~ pascalcuoq$ cat t.c
#include <stdio.h>

int main(int argc, char **argv)
{
  int y;
  printf("argc:%d\n", argc);
  y = --argc - argc--;
  printf("y:%d\n", y);
  return 0;
}
Macbook:~ pascalcuoq$ gcc t.c
Macbook:~ pascalcuoq$ ./a.out 1 2 3 4 5 6 7 8 9
argc:10
y:0

Это время, когда вы утверждаете, что в моем компиляторе есть ошибка (поскольку он не возвращает то же самое, что и ваш).

Macbook:~ pascalcuoq$ gcc -v
Using built-in specs.
Target: i686-apple-darwin9
Configured with: /var/tmp/gcc/gcc-5490~1/src/configure --disable-checking -enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.0/ --with-gxx-include-dir=/include/c++/4.0.0 --with-slibdir=/usr/lib --build=i686-apple-darwin9 --with-arch=apple --with-tune=generic --host=i686-apple-darwin9 --target=i686-apple-darwin9
Thread model: posix
gcc version 4.0.1 (Apple Inc. build 5490)

Аарон также пишет:

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

Совершенно верно! Я дал простейшее объяснение, почему можно получить 6: результат явно указан в C99 как неопределенное поведение, и это было и в более ранних стандартах.

и:

Наконец, покажите, пожалуйста, компилятор, который предупреждает об этой конструкции.

Насколько мне известно, компилятор не предупреждает о * (& x - 1) , где x определяется как int x; . Вы утверждаете, что эта конструкция действительна на языке C и что хороший инженер должен уметь предсказать результат, потому что компилятор не предупреждает об этом? Эта конструкция не определена, как и обсуждаемая.

Наконец, если вам абсолютно необходимы предупреждения, чтобы поверить в наличие проблемы, рассмотрите возможность использования инструмента проверки, такого как Frama-C . Ему необходимо сделать некоторые допущения, которых нет в стандарте, чтобы зафиксировать некоторые существующие практики, но он правильно предупреждает о - xx - и большинстве других неопределенных вариантов поведения C.

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

Наконец, если вам абсолютно необходимы предупреждения, чтобы поверить в наличие проблемы, рассмотрите возможность использования инструмента проверки, такого как Frama-C . Ему необходимо сделать некоторые допущения, которых нет в стандарте, чтобы зафиксировать некоторые существующие практики, но он правильно предупреждает о - xx - и большинстве других неопределенных вариантов поведения C.

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

Наконец, если вам абсолютно необходимы предупреждения, чтобы поверить в наличие проблемы, рассмотрите возможность использования инструмента проверки, такого как Frama-C . Ему необходимо сделать некоторые допущения, которых нет в стандарте, чтобы зафиксировать некоторые существующие практики, но он правильно предупреждает о - xx - и большинстве других неопределенных вариантов поведения C.

29
ответ дан 6 December 2019 в 04:56
поделиться

Я не знаю наверняка, но предполагаю, что это потому, что Java оценивает постдекремент на последнем x-- перед , оценивая оператор - =, тогда как C ++ вычисляет сначала - =, а постдекремент после выполнения всей остальной части выражения.

0
ответ дан 6 December 2019 в 04:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: