PureBasic - форум

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » PureBasic - форум » PureBasic для Windows » Нашёл ошибку передачи переменной по указателю


Нашёл ошибку передачи переменной по указателю

Сообщений 1 страница 13 из 13

1

ранее я  добился работы с переменной по ссылке, вот:

Код:
Procedure.s TrimRight(*a, n)
	Protected *p.string = @*a
	*p\s = Left(*p\s, Len(*p\s) - n)
EndProcedure


Define x.s = "Привет"
TrimRight(@x, 2)
Debug x

То есть в примере я передаю указатель на переменную, внутри функции создаю структуру из указателя, а структура состоит из указателя на указатель переменной, далее использую указатель на строку внутри функции.

На основе этого примера делаю другие функции и они перестают работать. Долго гадал и даже переделывал на вариант ниже:

Код:
Procedure.s TrimRight(*p.string, n)
	*p\s = Left(*p\s, Len(*p\s) - n)
EndProcedure

x.string\s="Тест"
TrimRight(@x, 2)
Debug x\s

Здесь создаётся структура "x" и передаётся указатель на структуру, а в функции создаётся структура из указателя на структуру. То есть фактически мы передаём указатель на уже созданную структуру, а функция принимающая просто использует уже готовые данные, просто имя структуры внутри функции своё, а указатель один и тот же.

Проблема в следующем: Если строку увеличить в длине, то внутренний механизм работы со структурой строки вызовет перевыделение памяти и после этого получит новый указатель на строку, но вернёт его внутрь структуры, то есть обращаясь к структуре мы берём новый указатель на строку, и когда функция завершена в структуре уже другой указатель на строку. То есть указатель на переменную и указатель на строку внутри структуры - это не один и тот же, поэтому Debug выведет старый указатель, данные которого за счёт перевыделения уже потеряны. Для примера в функции к *p\s = "больше чем" приравнять строку длиннее чем была и результат - потеря данных. Отсюда вывод, такой приём можно использовать если длинна строку будет короче чем исходная, в противном случае надо использовать структуру строки изначально.

0

2

Вы можете без попытки программировать, просто обычными словами написать, что вы понимаете под "работа с переменной по ссылке"?
И главное, в каких реальных конкретных случаях вам без этого нельзя обойтись?

p.s. вот этот код хоть теоретически даёт гарантию, что если вдруг Fred изменит реализацию(указатель на область в куче ограниченную нулём) хранения строк, то он продолжит работать.

Код:
EnableExplicit
Procedure Test(*p.String)
*p\s = Left(*p\s,Len(*p\s)-1) + "?"
EndProcedure


Define x.String
x\s = "Привет!"
Debug x\s
Test(@x)
Debug x\s

p.p.s. Через предопределённые структуры описывающие базовые типы в PB решается проблема не типизированных указателей, в отличие от других языков и в частности Си, где указатели связываются с типом переменной на которую указывают.

Отредактировано useful (06.03.2021 21:06:51)

0

3

useful написал(а):

что вы понимаете под "работа с переменной по ссылке"?

в AutoIt3 есть параметр ByRef, который прямо переводится "по ссылке" (by reference). Если писать функцию x=func(z), то мы делаем копию переменной, работаем с ней внутри функции и возвращаем в переменную x. А по ссылке, это нам не нужно делать z глобальной, чтобы к ней был доступ в функции, и функция остаётся изолированной и универсальной, а чтобы не копировать переменную выделяя ей ещё столько же памяти сколько она занимает, то мы передаём оригинал переменной внутрь функции, работаем с ней и не возвращаем результат, так как он уже возвращён во время работы с переменной внутри функции, потому что мы работаем с оригиналом переменной, а не с его копией.
Торможение заметно, когда работаем в цикле, чем больше шагов цикла и чем массивнее переменная, тем больше заметно торможение функции, поэтому и придуман способ работы с оригиналом и чтобы читалось это понятный языком. Ну в PureBasic видимо для этого надо работать со строкой в виде структуры.

Отредактировано AZJIO (06.03.2021 20:20:16)

0

4

Я то про ByRef / ByVal 40 лет знаю.
Не существует такого понятия "работать в виде структуры", я же написал какая проблема Фредом решается в данном случае через структуры.
Есть два пути, не лезть в дебри и пользоваться базовыми конструкциями языка или лезть в дебри и при написании той или иной конструкции любого языка программирования детально представлять во что эти структуры превращаются при трансляции(компиляции).
PB представляет возможность смотреть по средствам
-c, --commented, /COMMENTED: создаёт откомментированный файл вывода '.asm' при создании исполняемого файла. Этот файл можно впоследствии передать ассемблеру, после того как будут сделаны нужные изменения. Эта опция только для опытных программистов.

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

p.p.s. "Торможение заметно, когда работаем в цикле, чем больше шагов цикла и чем массивнее переменная, тем больше заметно торможение функции"
         И всё таки опять по возможности без кода а на словах опишите реальную вашу проблему.

Отредактировано useful (06.03.2021 21:11:18)

0

5

реальная проблема, вот например. Функция CopyMemoryString требует указатель на указатель, хотя по сути она не перевыделяет память, а копирует в уже выделенную нужного размера память. Зачем ей тогда указатель на указатель? Мне приходится, чтобы не плодить лишних переменных, так как мне всё равно надо делать структуру, то я сразу в функцию передаю структуру строки, кручу-верчу в туже переменную возвращаю. При чём раньше я казалось бы нашёл функцию с быстрым копированием данных и без создания структуры строки, а обычной переменной. Но поковырявшись в коде чуть позже обнаружил обычное выделение памяти *StrFast\Data = AllocateMemory(4 + 10240*SizeOf(Character)), причём не обязательно что этой памяти хватит под мои запросы, откуда чел взял, что для работы этого будет достаточно. Да, на удивление буфер stdout потока как раз оказался 10240, но я использовал эту функцию в свой программе, где данные не используют stdout, а просто наполняют некоторые данные размером до 1,5МБ. А расширенный вариант StrFastAddEx в его же коде использует ReAllocateMemory, то есть нет смысла менять CopyMemoryString  на ReAllocateMemory, так как всё равно приходится заранее высчитывать размер данных куда копировать. И самое интересное что нигде не написано, что в цикле делать конкатенацию строк это неправильный подход. У меня консольная утилита возвращает данные 20000 файлов за пол-секунды, а код PureBasic конкатенирует их 14 секунд, дольше чем утилита получает учитывая запросы на размер и даты.

Глобальная переменная затрудняет написание универсальных функций. Надо чтобы она не совпадала с переменными своего кода. Хотя думаю, в таком случае напишут в виде модуля, там наверно переменная будет глобальная только внутри модуля. Ну и ещё не понятно когда переменных много, затрудняет ли это быстрый доступ, например функция обратится к своим локальным переменным и из 3-х она быстро найдёт свою, а если будет обращаться к глобальным, не найдя в локальных, то будет перебирать глобальные.

Отредактировано AZJIO (06.03.2021 22:27:32)

0

6

Я хотел по пунктам разбирать все ваши заблуждения, но эта ваша фраза меня остановила.

AZJIO написал(а):

...Ну и ещё не понятно когда переменных много, затрудняет ли это быстрый доступ...

Так нельзя. Не первый раз обсуждаем на наших форумах (кажется на info мой ник jobless). Нельзя программировать не понимая базовые вещи, т.е. чем занимается и что получается после трансляции(компиляции) а чем далее занимается редактор связей.

AZJIO написал(а):

...а код PureBasic конкатенирует их 14 секунд ...

Так и Си это будет делать приблизительно так же по времени если использовать функции из stdlib. Организация строк одинаковая. И все эти функции для работы со строками сделаны для того, что бы не задумываться о придумывании алгоритмов в простых в смысле не критичных по времени выполнения случаях.

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

Вот это

AZJIO написал(а):

...я казалось бы нашёл функцию с быстрым копированием данных и без создания структуры строки...

для меня звучит очень странно.

В PB как в прочем и во многих других языках в случаях, когда стандартные(они так и называются, потому что для стандартных ситуаций созданы) функции не устраивают, то пишут свои и часто смешивать новые собственные и старые стандартные не желательно.
Например вы пишите, что CopyMemoryString требует указатель на указатель, а на самом деле The address of the string to copy. The string must be terminated with a null-character. The string is expected to be in the PB string format. (Адрес строки в памяти, которую требуется скопировать. Строка должна завершаться нулевым символом (#Null). Ожидается, что строка будет в формате строки PB.) Я уже выше отмечал, что всё может по меняться и Фрэд например возьмёт и вместо сишного принципа применит паскалевский, где вместо нуля в конце области хранится длинна строки рядом с указателем.

p.s. При всём моём критическом отношении к ситуации я точно не могу запретить вам придумывать то, что вы придумываете. Недавно на хабре прочитал. Зачем ты пишешь новую библиотеку если есть 100 старых на туже тему? Это тоже самое, что у композитора спросить (зачем?), сказав что уже есть сто песен на эту или иную тему. Просто новая песня в ноты должна попадать не хуже старых.

Отредактировано useful (07.03.2021 11:57:40)

0

7

AZJIO написал(а):

Отсюда вывод, такой приём можно использовать если длинна строку будет короче чем исходная, в противном случае надо использовать структуру строки изначально.

Може я что-то не понял, но работают оба варианта.

Код:
Procedure.s TrimRight(*p.string, n)
  *p\s = "1234567890asdfghjkl"
EndProcedure

x.string\s="Тест"
TrimRight(@x, 2)
Debug x\s

Procedure.s TrimRight_2(*a, n)
  Protected *p.string = @*a
  *p\s = "lkjuiop321654987"
EndProcedure


Define y.s = "Тест"
TrimRight_2(@y, 2)
Debug y

0

8

Автор то прав в конкретном случае. Второй работает, до тех пор пока не применил в процедуре какую то стандартную функцию работы со строками приводящую к пере выделению памяти.
Так это ОЧЕВИДНО! Я именно это и пытаюсь донести.

Вот это на жаргоне "грязный хак"

Код:
Procedure.s TrimRight_2(*a, n)
  Protected *p.string = @*a
.....

И нужно понимать что делаешь, а не писать нашёл ошибку.

Отредактировано useful (07.03.2021 12:27:10)

0

9

useful
:D ну я не имел ввиду что нашёл ошибку Фреда, скорее я начал понимать как работает функция и в чём я ошибался.
Грязный хак это понятие относительное, если функция может работать быстро, и у пользователя нет возможности изменить код, то грязный хак будет являться умных ходом. Пользователю ведь не объяснишь, почему в консоли утилита find отрабатывает 0,5сек 50 000 файлов, а в твоей утилите пол минуты. Нафиг такая утилита если её ждать приходится за каждый чихом. А значит нужно изучать устройство языка, чтобы знать как он себя поведёт в каких то случаях.

На счёт переменных, а что там понимать? Если их 100 штук и скомпилированное ищет её, даже если имена превратятся в адреса integrer, то это равносильно циклу, который перебирает 100 чисел пытаясь найти требуемое, то есть он перебирает числа из 3-х чисел локальных, потом из 100 глобальных. Логично, что из 3-х он выберет быстрей. И переменная ведь не одна добавляется, если человек переходит на концепцию не экономии, то он на каждый чих может добавлять переменную, то есть тут уже измеряется в процентах, например 30%. Ну и программа растёт в размере, ведь каждая переменная будет иметь минимум адрес и место для содержимого, а максимум текстовая информация в строках.

Я не говорю что Си будет быстрей PureBasic, они оба компилируют код в асм. Я про то что под стандартные задачи должны быть примеры. Я не думаю что Фред будет использовать конкатенацию переменными. Думаю это можно было указать в справке, чтобы это не происходило внезапно, что оказывается это надо делать иным способом.

А что если строка заканчивается Null, то она не может быть указатель на указатель? Я что противоречу чем-то?

The pointer to a variable holding the address with the destination buffer

и что мы видим? Указатель на адрес. Как говориться указатель и адрес это одно и тоже, оба указывают на что-то, оба являются адресом чего-то, просто игра слов. Но оба, адрес и указатель имеют тип integrer, только один адрес на строку, а второй указатель на адрес.

я точно не могу запретить вам придумывать то, что вы придумываете

запретить придумывать программы? Запретить придумывать быстрый способ получения данных? Запретить придумывать ошибку? В первом случае а нужно ли запрещать, во втором случае сначала надо доказать ошибку.

0

10

AZJIO написал(а):

Если их 100 штук и скомпилированное ищет её

Я дальше не стал читать. Или учиться или прекратить. Всё!
По мере сил могу помочь, пишите в ЛС.

0

11

Я так полагаю все из-за медленности строковых функций? Это из-за того что строки нультерминированные. https://ru.wikipedia.org/wiki/Нуль-терм … ная_строка
Длина строки не хранится и чтобы ее узнать нужно последовательно проанализировать все символы и найти завершающий нулевой байт (байты в случае юникода). В этом основная причина низкой скорости. Каждая строковая функция первым делом измеряет длину строки (аналог Len) и чем длиннее строка, тем больше на это нужно времени.
Если важна скорость можно написать аналоги строковых функций которые не будут измерять длину строки, но для этого нужно точно знать длину чтобы случайно не выйти за пределы строки.

0

12

Пётр
Память выделяется не бесконечная же. Наверно первый запрос на создание переменной или увеличение длины - перевыделяет память высчитывая её длину, а если строка укорачивается, то память либо перевыделяется под длину, либо остаётся той же длины, но внутри строка короче и читается до null. В общем вопрос не понятен. Если для памяти всё же выделяется размер, что зачем её определять по null, можно же просто в начало строки оставить данные для указания её длины.

так как вариант с созданием списка List и CopyMemoryString работает быстрее чем конкатенация, то предполагаю что под List буферезируется память. Из за чего число перевыделений памяти меньше и это работает быстрее. Ну или причина в том что увеличивая строку нужно перевыделять память на новое место перенося часто строку в другую позицию, а список как я понимаю имеет в предыдущем элементе указатель на следующий элемент и может размещаться в любом месте, то есть на каждый элемент выделяется память и пока элементы идут последовательно друг за другом то выделяется больший размер одним куском, а когда надо выделить в другом месте, то выделяется новый кусок, не копируя старый в новое место.

Отредактировано AZJIO (08.03.2021 00:22:35)

0

13

AZJIO написал(а):

то выделяется новый кусок, не копируя старый в новое место.

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

Отредактировано useful (08.03.2021 06:46:57)

0


Вы здесь » PureBasic - форум » PureBasic для Windows » Нашёл ошибку передачи переменной по указателю