2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу Пред.  1, 2, 3, 4  След.
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение29.08.2015, 02:55 
Заслуженный участник


04/05/09
4511
oleg_2 в сообщении #1048947 писал(а):
А Вы, venco, мне ответили: "Всё хорошо, всё нормально",
и сами же приводите такие ужасы, крэш и форматирование диска.
Это проблема C/C++ стандарта. Там есть такое понятие - "undefined behavior" или "неопределённое поведение". На то оно и неопределённое, что совершенно не определено, что же именно произойдёт. Поэтому и пугают крайними случаями, типа форматирования.
А недостаток в том, что некоторые операции всё-таки лучше было хоть как-то определить. Например, можно было застандартизировать, что инкремент указателя за пределы массива даёт неинициализированный указатель, т.е. обращение по нему как раз даёт неопределённое поведение, а остальные операции всего лишь оставляют его неинициализированным. В принципе, именно так реальные компиляторы и работают, а не следуют букве стандарта, считая любое неопределённое по стандарту поведение одинаково неопределённым. Поэтому я и сказал вам, что в данном случае это ерунда, и можно про неё забыть. Большинство про это место в стандарте даже не знает.

-- Пт авг 28, 2015 19:57:47 --

oleg_2 в сообщении #1048947 писал(а):
Верна ли моя догадка о быстроте первого варианта?
Второй вариант, кроме собственно работы с массивами, ещё обязан изменить внешнюю для функции структуру. Работы больше, соответственно и медленнее.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение29.08.2015, 08:51 


02/10/12
231
Большое спасибо.
Прошу ответить на два новых вопроса.

venco в сообщении #1048899 писал(а):
используйте беззнаковое смещение - size_t. У беззнаковых чисел переполнение однозначно определено и не приводит к undefined behavior:

Я думал, что при арифметических операциях с целыми числами есть только
один особый случай - деление на ноль. А всё остальное лишь переполнение
регистра, без каких-нибудь последствий. Не могли бы Вы пояснить, что Вы
подразумевали под этими своими словами "переполнение однозначно определено"?
Это первый мой вопрос.

Привожу написанную мной функцию преобразования строки в off_t-число.
Для off_t-чисел нет стандартных формата или функций преобразования, поэтому
самодельная. Сначала преобразует строку в off_t-число, а затем полученное число
преобразует как бы в строку и сравнивает эту строку с исходной. На самом деле
другой строки нет, а есть посимвольное сравнение с исходной. Так отлавливает
переполнение.
код: [ скачать ] [ спрятать ]
Используется синтаксис C

//   gcc parser_int.c -o parser_int.cgi -Wall -Werror -O3
//   ./parser_int.cgi

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

//------------------- my_a_to_off ---------------------
off_t
my_a_to_off(const char *str, int *err)
{
    off_t  s, d;
    const char *p1, *p0;
    int sig=0;

    *err = 0;
    p1=str;
    while(*p1==' ') p1++;
    if(*p1=='-'){ sig=1; p1++;}
    else if(*p1=='+') p1++;
    p0 = p1;
    s=0;
    while(*p1>='0' && *p1<='9'){
        s = (s * 10) + ((*p1++) - '0');
    }
    if(sig==1) s=-s;
    d=s;
    if(d<0){
        if(sig==0){ *err=1; return(s);}
        while(p1>p0){ if(*(--p1) != ('0'-(d%10))){ *err=1; break;} d = d/10;}
    }
    else while(p1>p0){ if(*(--p1) != ('0'+(d%10))){ *err=1; break;} d = d/10;}
    return(s);
}

//----------------- main -----------------------

int main()
{
    off_t d;
    int err;
    const char *ptr;
    char str[200];

    while(1){
        ptr = fgets(str, sizeof(str), stdin);
        if(ptr==NULL) exit(0);

        d = my_a_to_off(str, &err);
        printf("%lld; err = %d\n", (long long int)d, err);
    }
    exit(0);
}

// 9223372036854775807  max off_t
//-9223372036854775808  min off_t
 

Рассмотрим кусок кода этой моей функции:
Используется синтаксис C

    off_t  s;
    const char *p1, *p0;

    while(*p1>='0' && *p1<='9'){
        s = (s * 10) + ((*p1++) - '0');
    }
 

Этот кусок преобразует строку в off_t-число. Без проверки переполнения,
переполнение проверяется отдельно. Я знаю альтернативный код, вот он
(не опробован):
код: [ скачать ] [ спрятать ]
Используется синтаксис C

    off_t s, d; // остальные int

    s = 0;
    while (isdigit(*p1)) {
        digit = (*p1 - '0');
        if (sign > 0) {
            d = MAX_OFF_T / 10;
            if(s > d){ err = 1; break;}
            s = (s * 10);
            if((MAX_OFF_T - s) < digit){ err = 1; break;}
            s += digit;
        } else {
            d = MIN_OFF_T / 10;
            if(s < d){ err = 1; break;}
            s = (s * 10);
            if((MIN_OFF_T + s) > (-digit)){ err = 1; break;}
            s -= digit;
        }
        ++p1;
    }
 

Это хороший код, отлавливает "переполнение", но самого переполнения не бывает.
Тут комар носа не подточит, всё верно. Но надо знать константы, а в моей функции
их знать не надо. Я считал это моим преимуществом.
Когда я спрашивал: "Можно ли к указателю прибавить любое число?", то я
надеялся получить положительный ответ. Но ответ оказался скорее отрицательным,
чем положительным.
Теперь я спрашиваю: в этом моём куске кода, не альтернативном, а моём, а для него,
по замыслу, переполнение - это штатный режим, в нем переполняется не указатель, а
число, таится ли в этом моём куске кода какая-нибудь опасность? Это мой второй вопрос.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение29.08.2015, 14:05 
Заслуженный участник
Аватара пользователя


30/01/06
72408
venco в сообщении #1048911 писал(а):
Если быть параноиком, то по стандарту указатель может указывать только на элементы реального массива или непосредственно за ним. Компилятор имеет право вставить проверку при каждом изменении указателя, даже если вы его более не используете.
Но это именно паранойя. Если всё сделать в духе того, как декларировано в стандарте, то даже удалить память без undefined behavior невозможно, т.к. по возвращении из free() будет подвисший указатель, который, по идее, должен сразу привести к крашу. :-)

Не разводите панику. Достаточно всего лишь по возвращении из free() не использовать этот указатель, в том числе в арифметике указателей (а не только разыменовывая), и никакого undefined behavior не будет. Например, можно той же переменной присвоить 0 или другой указатель.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение29.08.2015, 15:53 
Заслуженный участник


04/05/09
4511
oleg_2 в сообщении #1048966 писал(а):
Я думал, что при арифметических операциях с целыми числами есть только один особый случай - деление на ноль. А всё остальное лишь переполнение регистра, без каких-нибудь последствий.
Та же проблема. По стандарту переполнение в арифметике со знаковыми числами - undefined behavior. И тоже никто не парится. Хотя недавно оптимизатор GCC так улучшили, что он как раз и сделал то, про что я писал - выкинул кусок кода, в котором было переполнение с int. Но это быстро исправили в компиляторе.

Кстати:
Используется синтаксис C
    while (isdigit(*p1)) {
 

Здесь тоже undefined behavior. Параметр функций типа isdigit() может иметь значение в диапазоне от -1 до 255, иначе - undefined behavior. А если тип char - знаковый (обычно), и в строке окажется не ASCII символ, например кириллица, то он при преобразовании к типу int даст отрицательное значение, которое в функцию isdigit() передавать нельзя. Но поскольку так делают очень часто, в стандартной библиотеке линукса ослабили это требование, и разрешили параметру иметь значение от -128 до 255. Хоть и undefined behavior.

Munin в сообщении #1049034 писал(а):
Не разводите панику.
Я же сказал "если всё сделать в духе того, как декларировано в стандарте". Идея этого ограничения в том, что CPU может обрабатывать указатели в особых регистрах со встроенной проверкой, и неправильный указатель просто нельзя будет в регистр записать. Но как раз в таком случае вызов функции free() должен стать проблемой - она должна освободить память, что сделает указатель в вызывающей функции не-валидным. Понятно, что так ничего не получится, поэтому такого ограничения в стандарте нет, хоть для этого компилятору на такой экзотической платформе придётся особенным образом обрабатывать функцию free() и аналогичные.
Ещё раз повторю - это если всё сделать в духе того, как декларировано в стандарте.

-- Сб авг 29, 2015 08:58:50 --

oleg_2 в сообщении #1048966 писал(а):
Теперь я спрашиваю: в этом моём куске кода, не альтернативном, а моём, а для него,
по замыслу, переполнение - это штатный режим, в нем переполняется не указатель, а
число, таится ли в этом моём куске кода какая-нибудь опасность? Это мой второй вопрос.
Да, т.к. off_t знаковый, то будет undefined behavior. Используйте беззнаковый тип того же размера, а знак смените в конце функции. Ну и ещё ваш первый вариант сильно медленнее второго.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение29.08.2015, 16:19 
Заслуженный участник
Аватара пользователя


30/01/06
72408
venco в сообщении #1049090 писал(а):
Я же сказал "если всё сделать в духе того, как декларировано в стандарте". Идея этого ограничения в том, что CPU может обрабатывать указатели в особых регистрах со встроенной проверкой

Даже если так, существование указателя в переменной, который стал инвалидным уже после того, как был записан в неё, стандартом не запрещается. Даже такие CPU не будут его проверять.

Смутно помнится даже, что на эту тему какие-то оговорки в стандарте были: мол, такое невалидное значение может спокойно существовать в переменной, но не может оттуда извлекаться и участвовать в операциях, а может быть только записано поверх другим значением, или быть уничтожено в деструкции переменной (например, по выходе из области действия). Но может, это где-то ещё было написано.

Более того, безопасность хранения таких величин в памяти гарантируется хотя бы существованием union-ов: в области памяти, которая может быть переменной-указателем, могут также лежать и биты, не имеющие никакого отношения к семантике указателя.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение29.08.2015, 22:26 


02/10/12
231
Спасибо.
Плохо, но ничего не поделаешь.

Вы, venco, уже дважды упоминали беззнаковые числа,
но не объяснили про них. Ваши слова: "У беззнаковых чисел переполнение однозначно
определено".

Пусть наш компьютер работает по стандарту, но наихудшим для нас образом.
Пусть для простоты sizeof(int)=1, sizeof(unsigned int)=1.
min_int = -128
max_int = +127
min_uint = 0
max_uint = 255
Что случится самое плохое?
код: [ скачать ] [ спрятать ]
Используется синтаксис C

    int a, b, c;

    a = 127;
    b = 2;
    c = a + b; // хорошо бы c = -127, но ожидаю падение программы

    a = -128;
    b = 2;
    c = a - b; // хорошо бы c = 126, но ожидаю падение программы


    unsigned int a, b, c;

    a = 255;
    b = 2;
    c = a + b; // ожидаю, что c = 1, программа не падает

    a = 1;
    b = 2;
    c = a - b; // ожидаю, что c = 255, программа не падает
 

Правильно ли я понял?

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение30.08.2015, 00:11 
Заслуженный участник


04/05/09
4511
На самом деле ничего страшного не произойдёт, так же как и с указателями. Но стандарт говорит, что переполнение чисел со знаком - undefined behavior, а переполнение беззнаковых чисел однозначно считается по модулю соответствующей степени двойки.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение30.08.2015, 02:47 
Заслуженный участник
Аватара пользователя


30/01/06
72408
Дело в том, что числа со знаком могут быть закодированы по-разному. Сейчас практически везде используется дополнение до двух, но стандарт позволяет реализовывать Си и на других архитектурах.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение30.08.2015, 05:26 
Заслуженный участник


04/05/09
4511
Munin в сообщении #1049209 писал(а):
Дело в том, что числа со знаком могут быть закодированы по-разному. Сейчас практически везде используется дополнение до двух, но стандарт позволяет реализовывать Си и на других архитектурах.
Для таких случаев в стандарте есть понятие "implementation dependent" (зависит от реализации), или "unspecified behavior" с перечислением допустимых вариантов, но в комитете почему-то выбрали "undefined behavior", что гораздо хуже, т.к. не даёт программисту вообще никаких гарантий.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение30.08.2015, 09:36 
Заслуженный участник
Аватара пользователя


30/01/06
72408
Да, вы правы. Видимо, речь о другом: о том, что вообще переполнения могут вызывать аппаратные прерывания и исключения, с каким угодно результатом (если среда выполнения о нём не позаботилась). Правда, вот тут уж то же самое относится и к переполнению беззнаковых... В общем, не могу понять, что было в голове у авторов стандарта.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение30.08.2015, 09:40 


11/12/14
893
Стандарт С++ зачастую очень мутная штука. :)
Просто из желания охватить как можно больше аппаратных решений.
Гарантий вроде как и нет, но если логически подумать, то "а по другому то и никак".
Например в memory model явно прописано, что память это один или несколько последовательных регионов, состоящих из байт.
Отсюда уже можно понимать, что под "array" можно понимать как раз каждый такой регион как минимум по отдельности. Т.е. требование на невыход за пределы массива +1 логично ослабляется. Хотя да, везде потом будет звучать только, что "array" в контексте что это именно array выделенный статически или динамически средствами языка.

Бардак даже в том, что если побуквенно втыкать в стандарт, то базовый макрос offsetof является "UB", потому что там "разыменование NULL-указателя".
Но при этом там нет на самом деле разыменования, если смотреть в стандарт Си, где явно говорится, что &*ptr "отменяют друг друга и равны в точности ptr".
И на самом в стандарте С++ про это начали заикаться, но не довели до в точности такой же формулировки.
А по другому то - никак.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение30.08.2015, 11:00 


02/10/12
231
venco в сообщении #1049198 писал(а):
переполнение беззнаковых чисел однозначно считается по модулю соответствующей степени двойки

Понял, спасибо.

(Оффтоп)

У меня есть
М. И. Болски, справочник по СИ, 1988 г., перевод с английского.
Про переполнение ничего нет. А про сдвиги написано:
М. И. Болски, Справочник. Язык программирования Си писал(а):
Сдвиг вправо может быть арифметическим (т. е. освобождающиеся
слева разряды заполняются значением знкового разряда)
или логическими в зависимости от реализации, однако
гарантируется, что сдвиг вправо целых чисел без знака будет
логическим и освобождающиеся слева разряды будут заполняться
нулями.

Справочник старый, но наверно и сейчас это так. Видимо, числа без знака
привилегированные.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение30.08.2015, 12:31 


11/12/14
893
oleg_2 в сообщении #1049240 писал(а):
Видимо, числа без знака
привилегированные.


Они не привелегированные, просто их машинное представление просто как пробка.

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение30.08.2015, 14:43 


02/10/12
231
В сообщении http://dxdy.ru/post1048966.html#p1048966
я приводил функцию преобразования строки в off_t-число. Она опасна.
Я переделал по второму варианту, с константами. Только где брать эти константы
- неведомо. А уж при переносе на другой компьютер неприятности обеспечены.
venco предложил "Используйте беззнаковый тип того же размера".
Рад бы, да, как говорится, хрен редьки не слаще.
Первый вариан был хоть и опасный, зато самодостаточный. Я попытался восстановить
самодостаточность:
Используется синтаксис C
    off_t MY_MIN_OFF_T, MY_MAX_OFF_T = 1;

    while((MY_MAX_OFF_T << 1) > 0){ MY_MAX_OFF_T = (MY_MAX_OFF_T << 1) + 1;}
    MY_MIN_OFF_T = -MY_MAX_OFF_T;
 

На моём компьютере это работает (только потерял максимальное отрицательное число).
Но нет ли тут какого-нибудь подвоха?

Вся функция:
код: [ скачать ] [ спрятать ]
Используется синтаксис C

//   gcc parser_off.c -o parser_off.cgi -Wall -Werror -O3
//   ./parser_off.cgi

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

//------------------- my_a_to_off ---------------------
off_t
my_a_to_off(const char *str, int *err)
{
    off_t  s, d;
    const char *p1;
    int sig=0, digit;
    off_t MY_MIN_OFF_T, MY_MAX_OFF_T = 1;

    while((MY_MAX_OFF_T << 1) > 0){ MY_MAX_OFF_T = (MY_MAX_OFF_T << 1) + 1;}
    MY_MIN_OFF_T = -MY_MAX_OFF_T;

    *err = 0;
    p1=str;
    s=0;
    while(*p1==' ') p1++;
    if(*p1=='-'){ sig=1; p1++;}
    else if(*p1=='+') p1++;
    if(sig==0){
        d = MY_MAX_OFF_T / 10;
        while(*p1>='0' && *p1<='9'){
            digit = ((*p1++) - '0');
            if(s > d){ *err = 1; break;}
            s = (s * 10);
            if((MY_MAX_OFF_T - s) < digit){ *err = 1; break;}
            s += digit;
        }
    }
    else{
        d = MY_MIN_OFF_T / 10;
        while(*p1>='0' && *p1<='9'){
            digit = ((*p1++) - '0');
            if(s < d){ *err = 1; break;}
            s = (s * 10);
            if((MY_MIN_OFF_T - s) > (-digit)){ *err = 1; break;}
            s -= digit;
        }
    }
    return(s);
}

//----------------- main -----------------------

int main()
{
    off_t d;
    int err;
    const char *ptr;
    char str[200];

    while(1){
        ptr = fgets(str, sizeof(str), stdin);
        if(ptr==NULL) exit(0);

        d = my_a_to_off(str, &err);
        printf("%lld; err = %d\n\n", (long long int)d, err);
    }
    exit(0);
}

// 9223372036854775807  max off_t
//-9223372036854775808  min off_t
 

 Профиль  
                  
 
 Re: СИ. Указатели. Можно ли к указателю прибавить любое число?
Сообщение30.08.2015, 16:03 


11/12/14
893
> Только где брать эти константы - неведомо.

Фигня-вопрос, во первых даже в stdlib есть куча констант типа INT_MAX, во вторых если проект кроссплатформенный, то там сразу принцип наименьшего противодействия заключается в вынесении платформенно-зависимых штук в отдельные файлы проекта, которые подключаются на разных платформах по разному. #ifdef - это еще наиболее простой способ такое делать.

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 60 ]  На страницу Пред.  1, 2, 3, 4  След.

Модераторы: Toucan, maxal, PAV, Karan, Супермодераторы



Кто сейчас на конференции

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


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group