Понимание интегральной арифметики в C - PullRequest
2 голосов
/ 08 марта 2019

РЕДАКТИРОВАТЬ: изменение void *, чтобы быть значениями uint8_t *. Проблема все еще сохраняется. РЕДАКТИРОВАТЬ: проблема была в переполнении простой переменной и не имела никакого отношения к целочисленным повышениям.

Я исправил ошибку в этом упрощенном куске кода. Типы такие же, как в исходном коде источника.

unsigned int entrySize;    // entrySize is 288
int startIndex, endIndex;  // both are 24536838
uint8_t *pStartAddr;          // valid initialized pointer (0x34f1e40)

/*Mystery begins...*/
uint8_t *curr_addr = pStartAddr + entrySize * startIndex;
while (curr_addr <= startAddr + entrySize * endIndex)
{
    externFunc(curr_addr);
    curr_addr+=entrySize;
}

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

Однако в одном из наших сбоев кажется, что curr_addr получает неверный указатель. Я догадывался, что есть проблема с entrySize * startIndex, поскольку их умножение устанавливается на 32-й бит, а наличие startIndex и endIndex в качестве типов со знаком может привести к путанице в компиляторе желаемого значения.

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

Я работаю на 64-битной машине, x86_64 CPU, gcc (GCC) 4.8.5 20150623 и дистрибутиве Linux Red Hat (версия 4.8.5-28)

Я предположил, что проблемы начинают возникать, когда вышеприведенное вычисление устанавливается на 32-й бит entrySize * startIndex. Однако, это все еще работало, когда я использовал первое значение startIndex, у которого был включен 32-й бит. Также отображается

Мои вопросы:

  • является результатом int*unsigned int, считающимся подписанным или без знака? какой ранг типа результата? умножение может вероятно, переполнение до 8-байтовых типов, и я предполагаю, что компилятор предотвращает потерять точность, верно?
  • startAddr - это void*. затем добавляется на любое значение и тип, рассчитанный в первом вопросе. это void* считается signed или unsigned? моя догадка, конечно, unsigned value, но я не могу ее подтвердить.
  • Какие целочисленные промоушены происходят в startAddr + << <strong>result >>.
  • Может ли наше заявление while никогда не останавливаться (на практике)? Если правая часть неравенства представляет собой число со знаком (шириной не менее 8 байтов), то будет ли левая сторона (curr_addr) повышаться как число со знаком, что приведет к бесконечному циклу?
  • Пошаговое объяснение будет приветствоваться:)

Я прочитал все, что содержится в этих ссылках, и все еще не знал:

  1. https://www.oreilly.com/library/view/c-in-a/0596006977/ch04.html
  2. Целочисленное преобразование ранга со знаком и без знака в int

Ответы [ 2 ]

5 голосов
/ 08 марта 2019
  1. Поведение арифметики указателя на void* не определено.

  2. Арифметика указателей действительна только для массивов. Обратите внимание, что вы можете установить указатель на один после последнего элемента массива, но не пытайтесь разыменовать его. Это правило также применяется к объектам, которые могут рассматриваться для целей правила как одноэлементные массивы.

(1) определенно не соблюдается в вашем коде (разве ваш компилятор не предупредил вас - если нет, bin это), (2) может не быть. Тогда, с некоторой шуткой (с моей стороны), ваши конкретные вопросы не имеют отношения.

2 голосов
/ 08 марта 2019

умножение может, вероятно, переполниться до 8-байтовых типов, и я предполагаю, что компилятор предотвращает потерю точности, верно?

Это одно неверное предположение. В соответствии с 3.4.3 описанием неопределенного поведения из стандарта C (добыча на болтах):

3.4.3

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

2 ПРИМЕЧАНИЕ. Возможное неопределенное поведение варьируется от игнорирования ситуации. полностью с непредсказуемыми результатами, чтобы вести себя во время перевода или выполнение программы документированным способом, характерным для среда (с выдачей диагностического сообщения или без него), чтобы прекращение перевода или выполнения (с выдачей диагностическое сообщение).

3 ПРИМЕР Примером неопределенного поведения является поведение на целое число Переполнение.

Целочисленное переполнение является примером неопределенного поведения, используемого в самом стандарте C.

И 288 * 24536838 равняется 7066609344, что значительно превышает возможности 32-разрядного int, будь то signed или unsigned, вызывая, таким образом, неопределенное поведение.

Так что нет, компилятор не "предотвращает потерю точности". Наоборот, на самом деле.

...