Функция, возвращающая неверное значение в C, используя компилятор gcc Intel

#c #function

#c #функция

Вопрос:

Я пишу это, потому что я действительно понятия не имею, почему это происходит. Я знаю, как это исправить / обойти, но я хотел бы знать причину, по которой это происходит. Я использую C, и мой компилятор — gcc 4.4.1 (TDM), работающий на компьютере Intel.

Предположения о плавающих значениях: -Соответствует стандарту IEEE 754 — Хранятся в виде большого конечного значения

Давайте предположим, что у нас есть функция, принимающая массив из 4 байт и возвращающая их в виде числа с плавающей запятой. Это цель функции. Давайте также скажем, что, например, все, что будет делать функция, это получать байты в «правильном порядке», а поскольку система имеет небольшой конечный код, она просто поменяет их местами и поместит их в число с плавающей точкой, чтобы вернуть значение. Для простоты я не включаю никаких проверок для NaN или INF, поскольку это не является целью этого вопроса.

 float testFunction(char* arr)
{
    //this will be the float we return
    float ret;

    //let's just get a char pointer to the float so we can alter its byte values
    char* c = (char*)amp;ret;
    //just swap them so they conform with little endian byte order
    c[0] = arr[3];
    c[1] = arr[2];
    c[2] = arr[1];
    c[3] = arr[0];

    //up to here if you debug and watch ret's value it is correct as it is supposed to be
    return ret;
}
  

Проблема везде, где я использую функцию… допустим, как показано ниже

    float f = testFunction(arr);
  

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

Способ успешно обойти это — объявить функцию, которая принимает значение с плавающей точкой в качестве параметра, и присвоить ей значение внутри функции следующим образом:

 void testFunction(char* arr,float* f)
{
   char* c = ((char*)f)
   c[0] = arr[3];
   c[1] = arr[2];
   c[2] = arr[1];
   c[3] = arr[0];
 }
  

Но все же мой вопрос в том, почему это происходит, когда я пытаюсь вернуть значение? Я понимаю, что float ret — это временное значение внутри области действия функции, но оператор return должен копировать его значение за пределы функции. Разве это не правильно?
Чего мне не хватает? Я думаю, это должно быть что-то действительно очевидное.

Комментарии:

1. Какой массив байтов вы передаете и какое float значение вы ожидаете?

2. Если мы считаем float , что это 32 бита (и char 8 бит), то то, что вы делаете, правильно.

3. @Charles: любой массив байтов, и я ожидаю, что значение с плавающей точкой соответствует стандарту IEEE 754. Просто используйте любой конвертер, чтобы увидеть, что вы должны указать в виде байтов и какого числа с плавающей точкой вы должны ожидать. Очень хороший здесь: h-schmidt.net/FloatApplet/IEEE754.html

4. Я попробовал вашу тестовую функцию, и она работает нормально для меня. По крайней мере, байты, которые я предоставил функции и в возвращаемом значении с плавающей запятой, были одинаковыми и имели правильный порядок. Поэтому попробуйте напечатать возвращаемое значение на вашем компьютере байт за байтом.

5. @Lefteris Так arr и есть BigEndian , верно? Потому что, если это уже LittleEndian так, вам не нужна замена.

Ответ №1:

На самом деле в обоих случаях значение с плавающей точкой должно иметь «совершенно нерелевантное» значение, если вы не тщательно обработаете этот массив символов (например memcpy , из a float ).

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

Комментарии:

1. Что вы имеете в виду? Я предполагаю, что представление с плавающей запятой соответствует стандарту одинарной точности IEEE 754, хранящемуся в формате little endian. Может ли это быть еще более разнообразным? -И второй случай, передача по ссылке, похоже, всегда работает в моей системе. Вот почему я немного запутался

2. @Lefteris Давайте скажем так: есть места, где есть C и нет IEEE 754. Когда вы задаете вопросы о C, всегда лучше написать в начале что-то вроде «Мне нужно, чтобы это работало на gcc (компилятор VC / Intel C) на Intel». Таким образом, никто не сможет бросить вам «стандарт C не говорит о том или ином». Затем вы вводите страну «конкретной платформы и конкретного компилятора». Чтобы понять, насколько «общими» являются стандарты C / C , прочитайте это parashift.com/c -faq-lite/intrinsic-types.html#faq-26.6

3. Да, вы правы, ксанатос. Я должен был указать это. Предоставленная вами статья представляет интерес для чтения. Потратит некоторое время на его чтение. Но вопрос все еще остается, кто-нибудь знает, почему это будет работать так, а не иначе?

Ответ №2:

Я скажу что-нибудь глупое. Но попробуйте инициализировать ret , например float ret = 0; .

Я не уверен, что вы можете инициализировать переменную «piecemail» char по одному и считать ее «инициализированной» для стандарта C (и компилятора)

Ответ №3:

WAG. Из-за приведения типов компилятор не распознает, что f переменная была обновлена. Для компилятора нет связи между f переменной и вашим c указателем. Он не знает, что это псевдоним. Поскольку в ABI значение float возвращается в регистре, оно должно генерировать загрузку из стека, когда оно выполняет return , но поскольку оно видит, что f оно не инициализировано, оно ничего не делает и возвращает случайное содержимое используемого для этого регистра. Если вы объявите float , как volatile это должно делать то, что вы ожидаете.

Как уже было сказано, это дикий a.. угадайте.

Комментарии:

1. ЭТО была хорошая догадка, но, к сожалению, по крайней мере, в моей системе это не сработало. Спасибо, что немного подумали об этом. Просто интересно узнать о двух ваших сокращениях: WAG — > ? ABI -> Двоичный интерфейс приложения?

2. WAG: догадка о дикой заднице ABI: двоичный интерфейс приложения соглашение, используемое операционной системой для передачи параметров в функцию и из функции, которой должен следовать компилятор.