Форум программистов, компьютерный форум, киберфорум
C/С++ под Linux
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.92/13: Рейтинг темы: голосов - 13, средняя оценка - 4.92
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30

[Задача] Очередной пример трудноуловимой ошибки

17.02.2012, 20:24. Показов 2606. Ответов 16

Студворк — интернет-сервис помощи студентам
Задача возникла на основе реальной ошибки на большой задаче, которую искали довольно-таки долго. Чтобы суть задачи осталась, но сам тестовый пример не был излишне громоздким, пришлось его несколько подсократить и привести в несколько другой вид, чем он был в нашем случае. Пример содержит внутри себя ошибку. Из-за специфичности ошибки накладываю ограничения на то, где и как запускать пример:
  • Пример запускать только под linux'ом на i386 с использованием компилятора gcc в режиме 32. Я не знаю, как оно поведёт себя на x86-64, потому что у меня его нет, проверить не могу. Может что-то выскочит, что послужит подсказкой. И точно знаю, что под виндами выскочит подсказка
  • Использовать только ту строку компиляции, которая приведена ниже. А то, как выяснилось, кое-кто любит подавать кучу опций, отвечающих за предупреждения, а предупреждения могут навести на подсказку. Во избежание лучше скопировать строку запуска, как она написана у меня. Всякими IDE не пользоваться, потому что они любят болтать по части предупреждений
  • На сервисах типа http://http://codepad.org/ просьба задачу НЕ запускать, т.к. там при компиляции используется куча всяких опций с предупреждениями

Без этих ограничений пришлось бы вырезать большой фрагмент кода и маскировать его дополнительными фрагментами кода, которые бы прятали ошибку, не вызывая при этом предупреждений при компиляции

Вопрос простой: объяснить поведение программы

ВНИМАНИЕ! Тем, кто запускает программу вне указанных ограничений, огромная просьба не писать ничего в теме а-ля "а у меня тут под MSVS выдаётся такое-то предупреждение, что бы это значило?" (а лучше вообще ничего не пишите). Задача из разряда "найти ошибку в программе", а подобными вопросами вы попросту лишаете возможности хорошо подумать тех, кому задача покажется интересной. И ещё одна традиционная просьба: прячьте под cut'ами свои предположения и размышления (из тех же соображений).

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <math.h>
 
int main (void)
{
  {
    double y1 = sin (0.1);
    printf ("point1: y1=%f\n", y1);
  }
 
  {
    void *p = (void*) (long long*) &y1;
    double *pp = (double*) p;
    printf ("point2: y1=%f\n", y1);
    printf ("point3: y1=%f *pp=%f\n", y1, *pp);
    printf ("point4: *pp=%f\n", *pp);
  }
 
  return 0;
}
Компиляция и запуск

Code
1
2
3
4
5
6
$ gcc t.c -w -lm
$ ./a.out 
point1: y1=0.099833
point2: y1=0.099833
point3: y1=-0.000000 *pp=-85187262145147854857819507968397748786961118259128041472.000000
point4: *pp=0.000000
9
Лучшие ответы (1)
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
17.02.2012, 20:24
Ответы с готовыми решениями:

Очередной неработающий пример из книги Шлее
Проблема состоит в том, что программа компилируется и появляются необходимые окна, но все завершается с ошибкой. Проблема заключается в...

Пример из книги выдает ошибки
Помогите разобраться выдает ошибки (если можно по подробнее. Если не трудно): win XP установлена. А пробую делать в Dev c++ ver 5.11 ...

Пример с учебника выдаёт ошибки
Здравствуйте, сейчас пытаюсь учить процессы/job-ы и т.д. Вот взял пример с учебника , вроде всё правильно переписал, но выдаёт кучу...

16
Псевдослучайный
1946 / 1146 / 98
Регистрация: 13.09.2011
Сообщений: 3,215
17.02.2012, 20:57
по рекомендации ТС
Ну в этом примере попытка использовать переменную вне блока, в котором она объявлена, бросается в глаза сразу, а вот почему собирается понять получается далеко не так быстро Интересно, каким образом удалось в реальном коде заткнуть варнинги?
0
 Аватар для Kastaneda
5232 / 3206 / 362
Регистрация: 12.12.2009
Сообщений: 8,143
Записей в блоге: 2
17.02.2012, 21:12
просто флуд
По моему, чтоб понять суть проблемы, нужно ну уж ооочень хорошо знать анатомию подключаемых файлов. Это я, как человек прочитавший таки предупреждения компилятора, говорю Без этого признаюсь не осилил бы (если я правильно понял суть проблемы. Надеюсь Evg нам все таки раскажет через пару дней)
0
Почетный модератор
7393 / 2639 / 281
Регистрация: 29.07.2006
Сообщений: 13,696
17.02.2012, 22:22
Evg, прикольная штука
размышления
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <math.h>
int main (void)
{
* {
* * double y1 = sin (0.1);
* * printf ("point1: y1=%f\n", y1); // здесь на стеке будет указатель на строку + 8 байт под дабл
* }
{
* * void *p = (void*) (long long*) &y1; // раз скомпилялось, значит фигня должна быть где-то еще объявлена. Как - хз. Но если бы было, float/double должно было быть что-то другое дальше... Значит там какая-нибудь муть мелкая. Судя по документации, мелкая муть в принтфе до 4-хбайтовых в общем случае приводится.
* * double *pp = (double*) p;
* * printf ("point2: y1=%f\n", y1);  // тогда на стеке здесь указатель на строку + 4 байта (?) неизвестного y1. Но так как мы используем %f то, документация говорит, что он как дабл (8 байт) берется, а значит принтф еще 4 байта захватит на стеке оставшиеся в наследство от передачи в предыдущий вызов.
* * printf ("point3: y1=%f *pp=%f\n", y1, *pp);
* * printf ("point4: *pp=%f\n", *pp);
* }
return 0;
}
Ну а дальше там все перепутается ))) Черкни, плиз, в личку, как там на самом деле, а то у меня на х64 такой байды не получается, а 32-х нет...
0
Псевдослучайный
1946 / 1146 / 98
Регистрация: 13.09.2011
Сообщений: 3,215
17.02.2012, 22:29
Vourhey, одинаково не работает(то есть работает не так, как ожидалось) на любой разрядности
спойлер
дело не в стеке, y1() есть в math.h. Мораль — переменным надо давать более осмысленные названия
0
387 / 151 / 16
Регистрация: 12.05.2011
Сообщений: 450
17.02.2012, 22:41
неожиданно
cut
man y1
0
Формучанин
364 / 296 / 42
Регистрация: 02.11.2010
Сообщений: 1,245
17.02.2012, 23:03
мои мысли по поводу задачи
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <math.h>
 
int main (void)
{
  {
    double y1 = sin (0.1);//8
    printf ("point1: y1=%f\n", y1);
  }
 
  {
 
    void *p = (void*) (long long*) &y1;//4
    double *pp = (double*) p;//8
 
//я думаю что переменная не должна быть видна в этой области, указатель может быть неверным
//Если указатель указывает непойми куда, то задача сильно облегчается, поэтому пусть указатель будет верным.
 
 
    printf ("point2: y1=%f\n", y1);
//в printf летит double (а оно 8 байт, видимо push+push )
//а во время работы printf прочтёт 4 байта и представит их как float (т.к. sizeof(float)==4)
//Однако вывод верный, видимо просто число такое и такое преобразование прокатило.
//илиже gcc преобразовал double во float и скормил printf - у,но я склоняюсь больше к первому варианту
 
 
//Видимо после предыдущего вывода переменная затёрлась в стеке.
//Кормим printf двумя 8-байтовыми переменными, однако прочтёт он две 4-х байтные переменные(float)
 
    printf ("point3: y1=%f *pp=%f\n", y1, *pp);
//printf прочтёт сначала первую часть(4 байта) переменной y1 а потом вторую(4 байта) и не доберётся до *pp 
 
 
//видимо переменная в стеке опять чуть поменялась
    printf ("point4: *pp=%f\n", *pp);
//читаем первые 4 байта  и выводим(нейпойми что) как float
  }
 
  return 0;
}
0
Почетный модератор
7393 / 2639 / 281
Регистрация: 29.07.2006
Сообщений: 13,696
17.02.2012, 23:05
Мдя...
Цитата Сообщение от NoMasters Посмотреть сообщение
одинаково не работает(то есть работает не так, как ожидалось) на любой разрядности
Логично. Я говорю о том, что на х64 не так не работает, как не работает у него. В частности, у меня все нули после первого же поинта, а это уже не так, как в задаче.
Цитата Сообщение от NoMasters Посмотреть сообщение
дело не в стеке, y1() есть в math.h
Ну и что, что она там есть? Как это на код-то влияет ты не написал. Дело не в том, что где-то что-то есть, а в том, как это используется
0
Псевдослучайный
1946 / 1146 / 98
Регистрация: 13.09.2011
Сообщений: 3,215
17.02.2012, 23:08
Vourhey
об этом я написал ещё в первом посте
0
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
18.02.2012, 00:01  [ТС]
Скажем так, в задаче на самом деле две подъё$ки. Первую нашли все. Но на всякий случай поясню:

Прикол N1
В линуксе в math.h есть описание функции с именем y1. Поэтому во втором блоке обращение к переменной y1 - на самом деле есть взятие адреса на эту функцию. Синус в программу был внесён только для того, чтобы оправдать появление math.h (чтобы не вызывало ненужных подозрений). В реальном коде warning'и не стреляли из-за того, что у нас стояло преобразование к int'у: "(int) y1". Т.е. преобразование к целому типу, по размерам совпадающему с указателем на функцию, тут никаких предупреждений после этого нет. Ошибку внесли в тот момент, когда в процессе наведения порядка переменную y1 из внешнего блока по невнимательности внесли во внутренний блок. После этого программа удачно скомпилилась и подставы никто не заметил


Теперь вопрос, по поводу прикола N2, который нашли далеко не все. Вопрос так же прячу, потому что он подразумевает раскрытие прикола N1, который, возможно, кому-то ещё предстоит найти

Вопрос N2
В точках "point1" и "point2" в printf подаются переменные y1, но в первом случае это настоящий double, а во втором - получился указатель на функцию. Вопрос: почему в обоих случаях printf напечатал одно и то же?
1
Higher
 Аватар для diagon
1953 / 1219 / 120
Регистрация: 02.05.2010
Сообщений: 2,925
Записей в блоге: 2
19.02.2012, 11:41
Цитата Сообщение от Evg Посмотреть сообщение
Вопрос N2
_
Это вроде UB. Порылся в википедии, там говорится, что стандартом оговаривается только случай, когда передаются лишние аргументы. А тут не совпадает размер типов( ожидается double, который весит 8 байт, а получает указатель, который весит 4 байта ). Возможно, из лишних 4 байт printf читает предыдущее значение. Но это уже догадки =\
0
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
19.02.2012, 15:14  [ТС]
Ответ на вопрос N2
Для простоты оригинальный пример можно сократить (чтобы остались только те моменты, которые интересны для данного вопроса). Как уже подметили, причина такой ситуации заключается в том, что во второй printf подаётся формат для double (%f), в то время, как реально передаётся указатель

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <math.h>
 
int main (void)
{
  double d = sin (0.1);
 
  printf ("%f\n", d);
 
  /* Далее будет показано, что принципиальной разницы нет,
   * указатель на что именно подавать */
  printf ("%f\n", &d);
 
  return 0;
}
Code
1
2
3
4
$ gcc t.c -w -lm
$ ./a.out 
0.099833
0.099833
При вызове первого printf'а передача параметра происходит следующим образом. Сначала в стек кладётся указатель на форматную строку (4 байта), затем в стек кладётся double (8 байт).

При вызове второго printf'а сначала в стек кладётся указатель на строку (4 байта), а затем указатель &d (тоже 4 байта). Однако при работе printf'а в тот момент, когда он разобрал форматную строку %f, он начинает из стека доставать аргумент типа double (т.е. 8 байт) и достаёт их. Но реально в этих 8 байтах в младших 4 байтах записан указатель (&d), а в старших 4 байтах лежит залипший остаток от старших 4 байт double'а, который остался от вызова первого printf'а. Я не буду глубоко вникать в формат хранения double'а, но факт в том, что в старшей половине double'а находится порядок (грубо говоря, 10 в какой степени), а в младшей - мантисса (грубо говоря, числа, после запятой). Таким образом получается, что в прочитанном double у нас сохранился порядок, но испортилась мантисса. На пальцах в десятичной форме это примерно означает, что было число 1.234567890, и последние 5 цифр из 10 затёрли и получилось 1.234500000. Т.е. получилось число, которое ненамного отличается от оригинального. Для формата %f у printf'а выводится только 5 десятичных знаков после запятой, и если попросить его напечатать больше, то разницу можно увидеть глазами.

Вот пример, который моделирует то, что у нас случилось с нашим аргументом функции:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <math.h>
#include <stdio.h>
 
int main (void)
{
  double d = sin (0.1);
 
  /* Оригинальное значение (то, что подавалось в первый printf) */
  printf ("%.20f\n", d);
 
  /* Портим младшую часть. Поскольку это цифры после запятой,
   * то нет принципиальной разницы, какое конкретно значение сюда
   * записать, а потому для простоты запишем 0 */
  *((int*)&d) = 0;
 
  /* Испорченное значение (то, что извлёк из стека второй printf) */
  printf ("%.20f\n", d);
  
  return 0;
}
Code
1
2
3
4
$ gcc t.c -w -lm
$ ./a.out 
0.09983341664682815475
0.09983336925506591797
1
19.02.2012, 15:32

Не по теме:

NoMasters, а теперь внимательно прочитай сообщение выше. Вкури его. Включи мозг и подумай, при чем тут стек. Упади на колени, проси прощения и сними с себя случайно прилипший значок "Эксперт С++".

0
19.02.2012, 17:11
Лучший ответ Сообщение было отмечено как решение

Решение

Не по теме:

Vourhey, на колени падать не стану, но значок отстрелю, раз уж такие дела.

0
19.02.2012, 17:27

Не по теме:

NoMasters, да я ж прикалываюсь :)

0
19.02.2012, 17:37

Не по теме:

Vourhey, в любом случае уж по плюсам-то я действительно не эксперт, теплый ламповой plain C с самописными велосипедами моё всё:)

0
Автор FAQ
 Аватар для -=ЮрА=-
6614 / 4256 / 401
Регистрация: 08.08.2009
Сообщений: 10,325
Записей в блоге: 24
08.04.2012, 09:42
Ответ под катом
Evg, с такой проблеммой столкнулся и решил её где-то лет 6 назад (когда был на 2-м курсе). Её суть: в коде присутствует переменная y1,
Цитата Сообщение от Evg Посмотреть сообщение
double y1 = sin (0.1);
в math.h существует функция с подобным именем
Вот кусок math.h
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#endif  /* ndef _MAC */
 
#ifndef __assembler /* Protect from assembler */
 
_CRTIMP extern double HUGE;
 
_CRTIMP double  __cdecl cabs(struct _complex);
_CRTIMP double  __cdecl hypot(double, double);
_CRTIMP double  __cdecl j0(double);
_CRTIMP double  __cdecl j1(double);
_CRTIMP double  __cdecl jn(int, double);
        int     __cdecl matherr(struct _exception *);
_CRTIMP double  __cdecl y0(double);
_CRTIMP double  __cdecl y1(double);
_CRTIMP double  __cdecl yn(int, double);
Надеюсь видно прототип функции _CRTIMP double __cdecl y1(double);
Проблемма решается в один присест выносом твоих функций в namespce Evg и вызововм их в программе посредством Evg::При этом конструкция using namespace std; никак не повлияет на код.
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <cmath>
using namespace std;
 
namespace Evg1
{
    double y1(double a, double b)
    {
        return a + b;
    }
}
 
namespace Evg2
{
    char y1[] = "BINGO";
}
 
int main()
{
    cout<<y1(5)<<endl;
    cout<<Evg1::y1(5,6)<<endl;
    cout<<Evg2::y1<<endl;
    system("pause");
    return 0;
}}
Ну чтоб всё было по правилам, вот код с конкретным рассмотрением y1 = sin(0.1)
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <cmath>
using namespace std;
 
namespace Evg
{
    double y1 = sin(0.1);
}
 
int main()
{
    cout<<y1(5)<<endl;
    cout<<Evg::y1<<endl;
    system("pause");
    return 0;
}
Отработка
0.147863
0.0998334
Для продолжения нажмите любую клавишу . . .
Миниатюры
[Задача] Очередной пример трудноуловимой ошибки  
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
08.04.2012, 09:42
Помогаю со студенческими работами здесь

Пример с учебника выдаёт ошибки
Здравствуйте. Есть такая задача( пример):Найти и вывести на печать координаты и значение минимума функции двух переменных f(x, y) =...

Есть пример.Поиск ошибки.dev-C++
Вечер добрый.Как правильно определить методы,нужно ли вызывать для массива name деструктор в операторе присваивания?При компиляции выдаёт...

У меня случаются ошибки. Как их избегать? пример с ObjectOutputStream
Вопросы в конце темы, можно сэкономить время можно прочитать сначала его. Пишу приложение клиент сервер. Клиент присоединяется к...

Пример LISP задача
Помогите пожалуйста, для очередной лабораторной работы скинули задачу вот такую. Найти все натуральные числа которые меньше либо равны N...

Ошибки с работой MessageBox.Show, пример из книги Стиллмен, Грин
После попытки выполнить первый же пример из книги столкнулся с проблемой. Все мои действия: 1. Проджект нью-&gt; Виндовс форм...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
17
Ответ Создать тему
Новые блоги и статьи
Оптимизация кода на разграничение прав доступа к элементам формы
Maks 13.04.2026
Алгоритм из решения ниже реализован на нетиповом документе, разработанного в конфигурации КА2. Задачи, как таковой, поставлено не было, проделанное ниже исключительно моя инициатива. Было так:. . .
Контроль заполнения и очистка дат в зависимости от значения перечислений
Maks 12.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "ПланированиеПерсонала", разработанного в конфигурации КА2. Задача: реализовать контроль корректности заполнения дат назначения. . .
Архитектура слоя интернета для сервера-слоя.
Hrethgir 11.04.2026
В продолжение https:/ / www. cyberforum. ru/ blogs/ 223907/ 10860. html Знаешь что я подумал? Раз мы все источники пишем в голове ветки, то ничего не мешает добавить в голову такой источник, который сам. . .
Подстановка значения реквизита справочника в табличную часть документа
Maks 10.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "ПланированиеПерсонала", разработанного в конфигурации КА2. Задача: при выборе сотрудника (справочник Сотрудники) в ТЧ документа. . .
Очистка реквизитов документа при копировании
Maks 09.04.2026
Алгоритм из решения ниже применим как для типовых, так и для нетиповых документов на самых различных конфигурациях. Задача: при копировании документа очищать определенные реквизиты и табличную. . .
модель ЗдравоСохранения 8. Подготовка к разному выполнению заданий
anaschu 08.04.2026
https:/ / github. com/ shumilovas/ med2. git main ветка * содержимое блока дэлэй из старой модели теперь внутри зайца новой модели 8ATzM_2aurI
Блокировка документа от изменений, если он открыт у другого пользователя
Maks 08.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа, разработанного в конфигурации КА2. Задача: запретить редактирование документа, если он открыт у другого пользователя. / / . . .
Система безопасности+живучести для сервера-слоя интернета (сети). Двойная привязка.
Hrethgir 08.04.2026
Далее были размышления о системе безопасности. Сообщения с наклонным текстом - мои. А как нам будет можно проверить, что ссылка наша, а не подделана хулиганами, которая выбросит на другую ветку и. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru