LINUX.ORG.RU

Си, учебная задача, массив, указатели

 , ,


0

3

Не знаю какими словами спросить у поисковых систем. Код ниже выдает такой результат:

690165708 14754882 1571426279 748212300 546573552 710529569 1908956059 1365401208 1566297428 705403694

690165708 14754882 1571426279 748212300 925961456 909718834 875771960 359798784 1566297428 705403694

По замыслу, ряды чисел должны совпадать не только в начале и конце списка. Собственно вопрос в том, как так получаеться, что массив полученный в функцию main, частично отличаеться от того, что вызванная функция создала.

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

int* GenTwoDigitRand(int qTty);

int main(int argc, char **argv) {
	int const mySize = 9;
	int* myArray = GenTwoDigitRand(mySize);
	
	for (int i = 0; i <= mySize; ++i) {
		printf("%d ", myArray[i]);
	}
	
	return 0;
}

int* GenTwoDigitRand(int qTty) {	
	srand(time(NULL));
	int myArray[qTty];
	int myRand = 0;
	for (int n = 0; n <= qTty; ++n) {
		myRand = rand();
		myArray[n] = myRand;
	}
	int *p = myArray;
	
	for (int n = 0; n <= qTty; ++n) {
		printf("%d ", myArray[n]);
	}
	printf("\n");
	
	return p;
}

Подскажите что прочитать или с каким запросом лезть в поисковик. Спасибо.



Последнее исправление: hobbit (всего исправлений: 1)

Почему современные компиляторы таки убогие?

int* GenTwoDigitRand(int qTty) {	
	int myArray[qTty];
	int *p = myArray;
	
	return myArray;
}

Тут warning есть warning: function returns address of local variable [-Wreturn-local-addr]

А тут уже нет

int* GenTwoDigitRand(int qTty) {	
	int myArray[qTty];
	int *p = myArray;
	
	return p;
}

Зато код реордерить умеют (чтоб всё сломать), а отследить элементарный алиасинг не способны.

anonymous
()
Ответ на: комментарий от jpegqs

Давайте-ка вы покажете пример, где int будет быстрее unsigned.

Ты ж сам только что показал прототип такого примера

Си, учебная задача, массив, указатели (комментарий)

там надо чуть скорректировать: нижнюю границу не нулём сделать, со входа убрать плохие числа и вот он пример: unsigned делает на одно сравнение больше.

firkax ★★★★★
()
Ответ на: комментарий от anonymous

Почему современные компиляторы таки убогие?

gcc и clang открыты, кинь патчи. Это ж, видимо, не сложно, просто никто не догадался. Заодно оптимизацию можешь пофиксить, чтоб не ломалось ничего. Сам-то я и Dragon book не осилил, а то б я ух!

JaneDoe
()
Ответ на: комментарий от Jullyfish

printf("%d%с", myArray[i], i != mySize-1 ? ' ' : '\n');

Лишний if в цикле (да, экономия на спичках, но всё же), плюс, что важнее, хуже читается, нежели в оригинале.

Я б только puts("") на putchar('\n') заменил. Опять же, и нагляднее, и, возможно, чуточку эффективнее, лень искать реализации.

JaneDoe
()
Ответ на: комментарий от anonymous

Понятия не имею. Я тут на работе с простеньким модулем «из базы в базу» за три дня чуть крышей не поехал, чтоб в этом году успеть, куда уж мне в компиляторы.

Но если два лучших их представителя до сих пор это не ловят, хотя случай точно не редкий, думаю, не так всё тривиально.

JaneDoe
()
Ответ на: комментарий от JaneDoe

Эти люди тратят кучу сил и ресурсов на обсуждение стандарта годами и при этом неспособны договорится о банальном варининге в трёх строчках с одним присвоением без volatile и побочных эффектов.

Выглядит как катастрофа.

anonymous
()
Ответ на: комментарий от JaneDoe

хуже читается, нежели в оригинале.

Поэтому и написал:

но это уже на вкус и цвет

Но всё же плодить лишний пробел в конце строки – такое себе.

и, возможно, чуточку эффективнее, лень искать реализации.

Скорее всего в разных реализациях по-разному.

Jullyfish
()

Ну по проблеме уже ответили. Я видел. Хотелось бы выразить свои мысли тоже.

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

Компилятор выскоуровневого языка скрывает, что происходит при вызове функции и возврате из неё. Скрывает, поскольку, программисты, когда кодили на асме, заметили, что делают одни и те же операции в этих двух случаях. Поэтому эти две рутинные операции можно было поручить компилятору и не париться.

Но «не париться» зашло слишком далеко. Новые поколения программистов уже не вдаются в эти детали, а зря. Там происходит очень важное таинство, объясняющее физический смысл локальных переменных.

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

Также и тут. Поручили компилятору вызывать функции. И забыли как это делается. :-)


Если коротко, смотри. Исторически сложилось, что твоей программе отдана вся память. В современных ОС это уже конечно не так, но они это умело эмулируют, ты ничего не заподозришь. В начале идет твой код. Потом идут данные. Затем идет куча. Ну кучу ты еще не попросил, у тебя ее нет. Поэтому где-то здесь проходит граница между твоими данными и стеком.

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

На стеке есть указатель, SP. Он так и называется Stack Pointer, это регистр процессора. Все адреса переменных в рамках текущей функции рассчитываются от текущего значения SP. А вот значение SP корректируется как раз при вызовах функций. При вызове функции – это называется преамбула. И обратная коррекция при возврате из функции я не помню как называется.

Поэтому переменные внутри функций, локальны. Потому что их адреса рассчитаны из местных значений Stack Pointer’a. Понимаешь?

Тут еще сложностей добавляет сама операционная система. Современные ОС защищенного режима со страничной организацией памяти, они могут уже отобрать страницу стека, которую ты использовал. Возможно в Dos бы твой код сработал. Но в современном мире уже слои абстракций, абстракция на абстракции и абстракцией погоняет.

hibou ★★★★★
()
Ответ на: комментарий от firkax

Ты ж сам только что показал прототип такого примера

Это был пример говнокода, который писать нельзя. Я уже упоминал что скорость полученная на UB нельзя считать ускорением. Если тот же пример переписать на unsigned, то проверок с границами массивов будет меньше (тоже давал пример).

Почему я против компиляторов спекулирующих на UB - скорость эту можно получить только на откровенно плохом коде. Обычно это старый легаси код, который работал правильно до их «оптимизаций». Но разработчиков компиляторов это не волнует. Им нужно выпускать новую версию компилятора и рисовать графики что производительность кода идёт вверх, и когда все способы уже исчерпаны - они начинают спекулировать на UB.

Также не все архитектуры умеют в чтение знаковых типов при чтении. Например в ARM (32-бит) до ARMv6 чтения всегда расширяют нулями. Поэтому char там по умолчанию используют беззнаковый для скорости (причем используют и в ARMv7 как наследие старой версии архитектуры). И «современный» Эльбрус не расширяет знаком при чтении, но я просил разработчиков это добавить.

Еще соглашение о вызове не всегда определяет что передаётся в старших битах, если число меньше размера указателя. Или может это компиляторы перестраховываются и всегда самостоятельно расширяют аргументы (x86, x86_64). Иногда соглашение о вызове определяет, но это зависит от архитектуры (и соглашения, у одной архитектуры могут быть разные соглашения о вызове). Например в mips64 и riscv64 - 32 бит значения передаются расширенные знаком (даже если это unsigned). Добиться чтобы компилятор точно не тратил время на на инструкции расширения - только используя long в качестве типа аргументов. Поэтому для MIPS и его наследника RISC-V лучше int.

Также разные 64-бит архитектуры по разному формируют результат 32-бит операции в 64-бит регистре.

  1. расширяют нулём (x86_64, arm64, e2k)
  2. расширяют знаком (mips64, riscv64, loongarch64)
  3. не трогают старшие биты (e2k в старом режиме)
  4. отсутствуют 32-бит операции, используется 64-бит операция (sparc64, power64)

P.S.: Robert C. Seacord, автор учебников по Си и участник ISO группы определяющей стандарт Си - советует использовать unsigned везде где можно, всё это из-за UB, и нападок на Си из-за этого UB со знаковыми целыми. Кого вы послушаете, его или тёмную личность с ЛОР советующую использовать int?

jpegqs
()
Ответ на: комментарий от hibou

При вызове функции – это называется преамбула. И обратная коррекция при возврате из функции я не помню как называется.

Про преамбулу не помню, но судя по описанию вы про пролог и эпилог.

Obezyan
()
Ответ на: комментарий от jpegqs

Давайте-ка вы покажете пример, где int будет быстрее unsigned.

А вы в чём-то правы - оказалось не так просто запровайдить реалистичный пример такого кода. Закинул удочку коллегам - посмотрим что скажут. А пока - смотрите какой треш и угар иногда генерится. Поменяйте там unsigned на int в цикле чтобы почувствовать разницу.

bugfixer ★★★★★
()
Ответ на: комментарий от Obezyan

Да, такие слова тоже припоминаю. И так, и этак встречалось наверное.

Вообще, конечно как заметил @mono, это все контент из старых свитков )))

Особенно в эру ИИ.

hibou ★★★★★
()
Ответ на: комментарий от hibou

Кажется, вся эта экономия – на спичках. По настоящему ускоряет вычисления алгоритм.

Согласен. Просто некоторые тут начали утверждать что int быстрее, и я привёл аргументы.

Спекуляции с UB в современных компиляторах - тоже экономия на спичках, больше вредит безопасности и открывает простор для критики языка, чем даёт производительности. Серьёзные проекты просто собирают код с опцией -fwrapv.

jpegqs
()
Ответ на: комментарий от jpegqs

некоторые тут начали утверждать что int быстрее, и я привёл аргументы.

Вы мне показали лишний register-to-register move который вы с большой вероятностью даже померять не сможете. Я вам показал миллионы лишних инструкций. И вы на голубой глазу продолжаете утверждать что беззнаковые быстрее, ага.

Серьёзные проекты просто собирают код с опцией -fwrapv.

А ещё есть -fno-strict-overflow. Но мне сейчас гораздо более интересно что вы серьёзными проектами называете, и за сколько из них вы головой отвечаете?

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

какой треш и угар иногда генерится.

21474837 * 100 = 0x80000034, компилятор всё правильно соптимизировал.

Поменяйте там unsigned на int в цикле чтобы почувствовать разницу.

Это вырожденная бессмысленная задача. Она и для unsigned может решаться без цикла:

return limit >= 21474837 ? 21474837 : 0;

А для int компилятор прямо спекулирует на UB, что счётчик и результат умножения никогда не уйдут в минус. Если код оптимизируется за счёт UB - значит это плохой код, а не хороший компилятор.

jpegqs
()
Ответ на: комментарий от bugfixer

Я вам показал миллионы лишних инструкций.

На бессмысленном тесте с UB?

Тогда вот алгоритм, который на int с ненулевым значением работает бесконечное время, на unsigned за конечное. Я победил. Это не миллионы инструкций, это вечность.

int ctz_int(int x) {
	int a = 32;
	if (x) do a--; while (x += x);
	return a;
}

int ctz_unsigned(unsigned x) {
	int a = 32;
	if (x) do a--; while (x += x);
	return a;
}
ctz_int:
        test    edi, edi
        jne     .L3
        mov     eax, 32
        ret
.L3:
        jmp     .L3
ctz_unsigned:
        xor     eax, eax
        mov     edx, 32
        rep bsf eax, edi
        test    edi, edi
        cmove   eax, edx
        ret
jpegqs
()
Ответ на: комментарий от jpegqs

Если код оптимизируется за счёт UB

UB там появляется исключительно на больших значениях limit. И вот, не буду я их туда подсовывать. И хочу чтобы в моих use-cases код был максимально быстрым.

Это вырожденная бессмысленная задача.

Вы хотели пример где int быстрее, я предоставил. И он там пипец насколько быстрее. Я уверен что подумав и поигравшись подольше я бы это оформил это во что нибудь более осмысленное, например условие внутри цикла могло бы быть какой-нибудь лямбдой, итд.

Но интересней всего мне в данный момент остаётся ответ на вопрос «что вы серьёзными проектами называете, и за сколько из них вы головой отвечаете?» с которого вы ловко соскочить пытаетесь.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Вы хотели пример где int быстрее, я предоставил. И он там пипец насколько быстрее.

Товарищи, пожалуйста, наставьте этому клоуну клоунов. Если вот это валидный пример. Его код доказывающий что int быстрее unsigned:

int test_int(int limit)
{
    for (unsigned i = 0; i < limit; ++i) {
        if (int(i * 100) < 0)
            return i;
    }
    return 0;
}

И он там пипец насколько быстрее.

Даже если он в миллионы раз быстрее, я вам предоставил пример, тоже с UB, где int бесконечно медленнее. Что больше, миллионы или бесконечность?

jpegqs
()
Ответ на: комментарий от jpegqs

тоже с UB

В моём примере нет UB. А вот замедление - есть, факт. И хоть обставьтесь клоунами - не трогает оно меня, от слова совсем. Только ваше бессилие и никчёмность демонстрирует.

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)