Войти

Показать полную графическую версию : Сумма трех байт


bilytur
25-09-2003, 03:14
Небольшая програмка на Си
Сумма трех байт: (Компилировалась на VC6)

char buf[] = {1,4,16};
char *p= buf;

int Sum1 = *(p++) + *(p++) + *(p++);

p= buf-1;
int Sum2 = *(++p) + *(++p) + *(++p);

printf("Sum1=%d *** Sum2=%d\n", Sum1,Sum2);

При компиляции в Debug выдает:Sum1=3 *** Sum2=21
В Release:Sum1=3 *** Sum2=48

Почему программка ведет себя по разному в Debug и в Release?
Почему она неправильно в 3-ех случаях из 4 подсчитывает сумму?
Должно быть 21 в смысле: buf[0]+buf[1]+buf[2]  = 21

Sarge
25-09-2003, 04:20
bilytur
Кофе попей, протри глаза, и внимательно посмотри, ЧТО ты складываешь:
В первом случае ты складываешь, то что надо - buf[0]+buf[1]+buf[2]    (p=buf)
Во втором случае buf[-1]+buf[0]+buf[1]     (p=buf-1   :o)
Так как buf[-1] имеет неопределённое значение, у тебя получаются разные результаты.

Добавлено:

bilytur
Советую также обратить внимание (во избежание другого глюка) на то, что у тебя результат будет CHAR, а не INT, как ты хотел (если ты именно так хотел), возникнет переполнение. Чтобы этого избежать, надо написать int Sum1 = int(*(p++)) + *(p++) + *(p++);

shurikan
25-09-2003, 06:44
Sarge
Чё-то у тебя со знанием языка не того... Про постфиксные и префиксные операции слышал чего-нибудь? Сумма char-ов - действительно имеет тип char, н6о это если в арифметическом выражении не присутствуют операнды других типов, тогда будет сделано приведение типов. А здесь приведение производится при присваивании. Вопрос был почему написанное не даёт правильный результат?
bilytur
Трудно так сразу сказать. Я бы скомпилировал в ассемблер, и посмотрел, чё там VC имеет ввиду, обрабатывая этот текст. Там можно будет понять, когда он указатели продвигает, и на что они в тот момент указывают.


Добавлено:

А вообще C/C++ не любит таких наворотов. Он проводит оптимизацию при компиляции, а поскольку в выражении все операции равноправны, он может вычислять их в любом порядке, а не слева направо. И, казалось бы, да складывай их в любом порядке, они же одинаковые (операнды, я имею ввиду), но там ещё указатель перемещается, а это он как-то не очень любит. Так что, скомпили в ассемблер...

unknown bug
25-09-2003, 12:20
Ассемблер рулез :gigi:
Скомпилил твой пример и как оказалось в релизе сложения нет вообще. Вот сам код:

push * *30h ; в десятеричном соответственно 48
push * *3
push * *offset aSum1DSum2D ; "Sum1=%d *** Sum2=%d\n"
call * *sub_401014 ; это типа printf *:)


Как видишь, никакого сложения нет. Компилятор сам вычислил значения Sum1 и Sum2 и заменил их вычисления уже числами...

Удачи :biggrin:

ivank
25-09-2003, 17:29
Тут дело в том, что порядок выполнения операций между точками следования не определён. Из фака su.c_cpp:
>> 036. k[++j] = k[j-- - 1] + k[++j]; Как это считается?
> Q: Отгадай загадку, All. Как будет выглядеть массив k после:
>
>      int k[] = { 0, 1, 2, 3, 4, 5 };
>      int j = 2;
>
>      k[++j] = k[j-- - 1] + k[++j];
>
>    Проверил на трех компиляторах - MSVC++ 6.0 SP3, BC 3.1,
>    Watcom 11.0 - и в C и в C++ режимах. Ответы разные у всех компиляторов!

A: (Serge A. Rider) - 30.10.1999

    Лень глядеть в стандарт, но вот из Страуструпа:
-------------------------------
6.2.2 Evaluation Order
The order of evaluation of subexpressions within an expression is
undefined. In particular, you cannot assume that the expression is
evaluated left to right.
-------------------------------

Короче говоря, в твоем варианте операции ++j, j-- и ++j могут
выполняться в любой последовательности - как захочется компилятору -
со всеми вытекающими. Исключение составляют только ",", && и || - они
всегдя слева направо.

(Hе только эти. Есть еще тернарная операция `?:'. -- Ivan Kosarev (31.10.99))

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


Добавлено:

Кстати, так тоже нельзя писать:
char buf[] = {1,4,16};
char * p = buf-1;

Т.е. нельзя ничего вычитать из адреса нулевого элемента массива. Это тоже может привести к гипотетическим граблям на некоторых архитектурах. (Например на x86 не в pmode, если у тебя массив лежит в самом начале сегмента, то будут проблемы).

bilytur
26-09-2003, 00:56
Большое спасибо всем.

ivank
_Вам_ отдельный респект за полный и исчерпывающий ответ.

Sarge
27-09-2003, 14:22
shurikan
1. Если тебе трудно сказать, то с твоим знанием СИ всё понятно. Я очень даже доходчиво написал, в чём проблема: p=buf-1 - во втором случае складываются не 0-й, 1-й и 2-й элемент
массива, а -1-й, 0-й и 1-й ! А -1-й элемент может быть чем угодно, он не инициализирован.
2. Типы в проге приводятся уже ПОСЛЕ вычисления результата. Напиши такую прогу, например:
unsigned char a=200, b=300;
int c=a+b;
Ты думаешь, что c=500 ?! Сначала вычислится результат с типом char, это 244 (переполнение через байт), а потом уже приведётся к типу int.
unknown bug
Ты немного выше по коду смотреть не пробовал? Это код printf и ВСЁ. Код сложения выше.

bgg0408
28-09-2003, 01:40
to Sarge
Я конечно извиняюсь, но проблемы с Си у Вас:) По тексту видно, что вторая строка (с Sum2) выдает правильный ответ, а первая -нет.
buf[-1]
Ну, и где он там?
6.2.2 Evaluation Order
Вот такое было. Никто не может сказать, что на самом деле суммируется:(

Насчет кода - правда. Это код вызова функции printf, хотя кто знает:(, что там компилятор делает, оптимизируя код. Он так извращает код, что иногда оптимизацию приходится выключать:(

Исправлено: bgg0408, 2:45 28-09-2003

bilytur
28-09-2003, 02:25
Sarge Имхо, ты не прав.
Немного перепишем программку, чтоб она соответствовала  Страуструпу.
И введем char *p раньше, для того что-бы buf[] не лежал в начале сегмента на некоторых архитектурах

char *p;
char buf[] = {1,4,16};

p= buf-1;

int Sum2  = *(++p);
   Sum2 += *(++p);
   Sum2 += *(++p);  

Ты серьезно считаешь что это вычисляет  buf[-1]+buf[0]+buf[1] чтоли?

А переполнения в конкретном случае 1+4+16 тоже небыло.

shurikan
28-09-2003, 03:08
Sarge
Неохота флеймить, но...
2. Типы в проге приводятся уже ПОСЛЕ вычисления результата. Напиши такую прогу, например:
unsigned char a=200, b=300;
int c=a+b;
Ты думаешь, что c=500 ?! Сначала вычислится результат с типом char, это 244 (переполнение через байт), а потом уже приведётся к типу int.
Это что такое? Какого типа вообще константы 200 и (особенно) 300? В примере-то вообще был указан тип char, а не unsigned char. Последний имеет своим максимумом значение 255, как ты собираешься запихнуть туда 300? А..., ладно...
:down:

Добавлено:

Кстати, вспомни, как вычисляются выражения типа *(++p) и *(p++)...




© OSzone.net 2001-2012