فهرست مقاله
در عیبیابیِ ماندگاریِ داده، گاهی ظرفِ اصلی را میبندیم اما یک راهِ دسترسی به محتوای آن هنوز نگه داشته شده است. مسئله فقط بزرگیِ محموله نیست؛ باید بدانیم کدام دسترسی زنده مانده و پایانِ عمرِ کدام شیء را عقب میاندازد. این درس با چهار بایتِ ساختگی، نمای اصلی، برشِ نگهداشتهشده و یک نسخهٔ مستقل، مرزِ استفاده از بافر را بررسی میکند. فایل، حسگر و ارتباطِ واقعی نداریم؛ همهٔ رویدادها در حافظه و در یک مسیرِ ترتیبی رخ میدهند.
۱. مالکِ بافر و عمرِ دسترسیِ قرضی

شیء BytesIO یک جریانِ بایتی در حافظه است. طبق قراردادِ getbuffer، نمایی خواندنی و نوشتنی از محتوای آن میگیریم، بدون اینکه خودِ محتوا برای ساختِ این دسترسی کپی شود. «قرضی» در این درس یک تشبیهِ مالکیت است: مصرفکننده، راهِ دسترسی دارد و نباید فرض کند بافرِ زیرین را مستقل از مالک میتواند بزرگ یا نابود کند.
نمای اصلی را صفر، برش را یک و نمای فقطخواندنیِ برش را دو نامگذاری میکنیم. این شمارهها نشانیِ حافظه نیستند؛ کلیدهای کوچکِ دفترِ آزمایشاند. ورودی دقیقاً bytes است و حداکثر شانزده بایت دارد. برنامه حداکثر چهار نام و دوازده رویداد را میپذیرد. نوعِ bool بهجای عدد، نامِ تکراری، والدِ معرفینشده و بایتِ خارج از صفر تا ۲۵۵ رد میشوند؛ این سقفها سیاستِ آموزشیاند.
در حسابداریِ اندازهٔ اشیای مشترک، پرسش این بود که کدام شیء را یک بار بشماریم. اینجا اندازهگیری انجام نمیدهیم؛ میپرسیم کدام دسترسی مانعِ پایانِ استفاده از بافر است. طولِ محموله بر حسب بایت و تعدادِ نما بیبُعد است. هیچکدام، حافظهٔ انحصاری، مصرفِ کلِ فرایند یا مقدارِ آزادشده در سیستمعامل را تعیین نمیکند.
۲. تغییرِ محتوا با تغییرِ اندازه فرق دارد

محمولهٔ دستی، چهار نویسهٔ ASCII برابر ABCD است؛ نمایشِ شانزدهیِ بایتهایش 41424344 میشود. برشِ یک تا چهار، سه بایتِ BCD را میبیند. نوشتنِ مقدارِ ۹۰ در اندیسِ دو، یعنی سومین بایتِ نمای اصلی، C را به Z تبدیل میکند. مرزهای بافر ثابت میمانند و هر دسترسیِ زنده به آن ناحیه، محتوای تازه را میخواند. این ویرایش با افزودنِ یک بایت به انتهای جریان فرق دارد.
مستنداتِ memoryview انتسابِ یکبعدیِ هماندازه را توضیح میدهد؛ جایگزینیِ یک خانه با دو بایت مجاز نیست. در آزمایشِ جدا، دو خانه را با XY عوض میکنیم و سپس انتسابِ بدساختار را با خطای ValueError میسنجیم. برای افزودن، ابتدا جایگاهِ جریان را به انتها میبریم و دقیقاً یک بایت مینویسیم؛ دربارهٔ مجازبودنِ تمامِ نوشتنهای هماندازه از مسیرِ خودِ جریان حکمی نمیدهیم.
تا وقتی نما نگه داشته شده، قراردادِ BytesIO تغییرِ اندازه و بستن را منع میکند. در اجرای این نمونه، تلاشِ رشد و تلاشِ close هر دو BufferError میدهند و خواندنِ نمای موجود همچنان کار میکند. ردشدنِ بستن را با بستهشدنِ موفق اشتباه نمیگیریم. برنامه نامِ نوعِ خطا را ثبت میکند، نه متنِ پیامِ وابسته به نسخه را؛ دادهٔ قبلی نیز برای بررسیِ اثرِ تلاشِ ناموفق دوباره خوانده میشود.
فقطخواندنیکردنِ نما، بافر را منجمد نمیکند. نوشتن از همان نمای محدود TypeError میدهد، اما تغییر از نمایِ نوشتنی در خواندنِ بعدی دیده میشود. آزمایشِ پنجرهٔ مشترکِ NumPy این تفاوت را برای آرایه نشان میداد. اینجا موضوعِ تازه، باقیماندنِ وابستگیِ عمرِ جریان حتی برای نمای فقطخواندنی یا برشِ تهی است.
۳. نمایِ مشتق و آزادسازیِ هر دسترسی

رویدادِ release روی نمای صفر، دسترسیِ بعدی از همان نما را نامعتبر میکند. مستندات، تکرارِ release را مجاز و دیگر عملیاتِ پس از آن را خطای ValueError معرفی میکند. این پایان را به همهٔ نماهای مشتق تعمیم نمیدهیم: برشِ نگهداشتهشده شیءِ نمای دیگری است. در شاهدِ اصلی، پس از آزادسازیِ صفر هنوز نمای دو، BZD را با نمایشِ 425a44 میخواند.
نخستین close شکست میخورد. بعد نمای یک را آزاد میکنیم، ولی نمای فقطخواندنیِ دو هنوز باقی است و دومین close نیز شکست میخورد. پس از آزادسازیِ دو، بستن موفق میشود. بنابراین ترتیبِ آزادسازیِ نامها بهتنهایی کافی نیست؛ باید همهٔ دسترسیهای نگهداشتهشده پایان یابند. برشِ تهی هم در اجرای محدودِ ما این وابستگی را نگه میدارد، با اینکه هیچ بایتِ منطقی برای خواندن ندارد.
ساختارِ with برای memoryview راهِ روشنِ آزادسازیِ همان نما هنگامِ خروج، از جمله خروج با استثناست. اما اگر برش را بیرون نگه داریم، خروج از with والد آن برش را آزاد نمیکند. آزمون عمداً چنین وضعی میسازد، خطای بدنه را میگیرد و سپس وابستگیِ باقیمانده را بررسی میکند. در پایان، خودِ برش صریحاً آزاد میشود؛ به زمانِ جمعآوریِ زباله تکیه نمیکنیم.
برای تحویلِ محتوای ثابت به مرحلهٔ بعد، در نمونه از tobytes() استفاده میکنیم. نسخهٔ ABCD پیش از ویرایش ساخته میشود؛ نمای زنده بعداً AZCD را میبیند، ولی bytes قبلی همان مقدارِ اولیه را نگه میدارد و پس از بستنِ جریان قابلاستفاده است. این انتخاب، وابستگیِ مصرفکننده به عمرِ همین بافر را برمیدارد؛ کلِ مسیر را بدون کپی، اتمی یا مناسبِ نویسندهٔ همزمان اعلام نمیکند.
۴. کدِ کامل، شاهدِ دستی و مرجعِ مستقل

کدِ زیر فقط کتابخانهٔ استاندارد میخواهد. run_schedule ابتدا شکل و کرانِ رویدادها را کامل کنترل میکند و سپس آنها را اجرا میکند. مرزِ برش از طولِ واقعیِ والد فراتر نمیرود؛ در این قرارداد، برخلاف برشِ عمومیِ پایتون، کوتاهشدنِ خاموشِ مرز را نمیپذیریم. خطای یک عملیات در گزارش میماند و اجرای رویدادِ بعدی ادامه دارد. بلوکِ finally همهٔ نماهای ساختهشده را آزاد و جریان را میبندد.
"""Finite BytesIO exported-view schedules, not a memory or speed measurement.
Only the registered views exist: no view escapes the runner. A synchronous
scalar-list/lease-table reference supplies expected contents and outcomes.
"""
import io
import itertools
import json
import platform
import sys
MAX_BYTES = 16
MAX_VIEWS = 4
MAX_EVENTS = 12
def validate(payload, events):
if type(payload) is not bytes:
raise TypeError('exact bytes payload required')
if len(payload) > MAX_BYTES:
raise ValueError('payload exceeds16 bytes')
if type(events) is not tuple:
raise TypeError('exact tuple of event tuples required')
if len(events) > MAX_EVENTS:
raise ValueError('at most12 events')
defined = set()
arity = {'open': 2, 'slice': 5, 'readonly': 3, 'read': 2,
'snapshot': 2, 'write': 4, 'release': 2, 'append': 2, 'close': 1}
for event in events:
if type(event) is not tuple or not event or type(event[0]) is not str:
raise TypeError('each event is a nonempty exact tuple with string op')
op = event[0]
if op not in arity or len(event) != arity[op]:
raise ValueError('unknown operation or wrong arity')
if not all(type(value) is int for value in event[1:]):
raise TypeError('all event arguments are exact int, not bool')
if op in ('open', 'slice', 'readonly'):
name = event[1]
if not 0 <= name < MAX_VIEWS or name in defined:
raise ValueError('view names0..3, defined only once')
if op != 'open' and event[2] not in defined:
raise ValueError('parent must be previously declared')
defined.add(name)
if op in ('read', 'snapshot', 'write', 'release') and event[1] not in defined:
raise ValueError('view must be previously declared')
if op == 'slice' and not 0 <= event[3] <= event[4] <= MAX_BYTES:
raise ValueError('ordered nonnegative slice within16 positions')
if op == 'write' and not 0 <= event[2] < MAX_BYTES:
raise ValueError('write index0..15')
if op in ('write', 'append') and not 0 <= event[-1] <= 255:
raise ValueError('byte value0..255')
def run_schedule(payload, events):
"""Validate schema first; retain actual operation errors by class name.
A view name denotes this runner's private handle. Reading a failed creation
yields our ValueError. Slice bounds use the actual parent length, not Python
slice clamping. Append always attempts growth by one byte, never overwrite.
"""
validate(payload, events)
stream, views, trace = io.BytesIO(payload), {}, []
def view(name):
if name not in views:
raise ValueError('no successfully created view for this name')
return views[name]
try:
for event in events:
op, result = event[0], None
try:
if op == 'open':
views[event[1]] = stream.getbuffer()
elif op == 'slice':
source = view(event[2])
if event[4] > len(source):
raise ValueError('slice exceeds actual parent length')
views[event[1]] = source[event[3]:event[4]]
elif op == 'readonly':
views[event[1]] = view(event[2]).toreadonly()
elif op in ('read', 'snapshot'):
result = view(event[1]).tobytes().hex()
elif op == 'write':
view(event[1])[event[2]] = event[3]
elif op == 'release':
view(event[1]).release()
elif op == 'append':
if len(stream.getvalue()) == MAX_BYTES:
raise ValueError('own16-byte result cap')
stream.seek(0, 2)
stream.write(bytes([event[1]]))
else:
stream.close()
trace.append({'op': op, 'outcome': 'ok', 'value': result})
except (BufferError, ValueError, TypeError, IndexError) as error:
trace.append({'op': op, 'outcome': type(error).__name__, 'value': None})
return trace
finally:
for handle in views.values():
handle.release()
stream.close()
def independent_reference(payload, events):
"""Trusted declared schedules: scalar content and leases, no buffer APIs.
No production validation/runner/helper is called. All registered handles
cover one-dimensional byte ranges. A lease is active independently of the
parent from which it was derived. This is not a general exporter oracle.
"""
data, leases, closed, trace = list(payload), {}, False, []
for event in events:
op, error, value = event[0], None, None
if op == 'close':
if any(lease['active'] for lease in leases.values()):
error = 'BufferError'
else:
closed = True
elif op == 'append':
if closed or len(data) == 16:
error = 'ValueError'
elif any(lease['active'] for lease in leases.values()):
error = 'BufferError'
else:
data.append(event[1])
elif op == 'open':
if closed:
error = 'ValueError'
else:
leases[event[1]] = {'begin': 0, 'end': len(data), 'readonly': False, 'active': True}
else:
source_name = event[2] if op in ('slice', 'readonly') else event[1]
source = leases.get(source_name)
if source is None or (not source['active'] and op != 'release'):
error = 'ValueError'
elif op == 'release':
source['active'] = False
elif op == 'slice':
if event[4] > source['end'] - source['begin']:
error = 'ValueError'
else:
leases[event[1]] = {'begin': source['begin'] + event[3],
'end': source['begin'] + event[4], 'readonly': source['readonly'], 'active': True}
elif op == 'readonly':
leases[event[1]] = {'begin': source['begin'], 'end': source['end'],
'readonly': True, 'active': True}
elif op == 'write':
if source['readonly']:
error = 'TypeError'
elif event[2] >= source['end'] - source['begin']:
error = 'IndexError'
else:
data[source['begin'] + event[2]] = event[3]
else:
value = ''.join(format(data[i], '02x') for i in range(source['begin'], source['end']))
trace.append({'op': op, 'outcome': error or 'ok', 'value': value if error is None else None})
return trace
def rejects(kind, function, *args):
try:
function(*args)
except kind:
return True
return False
def assign_first(target):
target[0] = 0
def mismatched_assignment(target):
target[1:2] = b'XY'
def snapshot_handoff():
stream = io.BytesIO(b'ABCD')
with stream.getbuffer() as parent:
with parent.toreadonly() as child:
snapshot = child.tobytes()
parent[1] = ord('Z')
changed = child.tobytes()
stream.close()
return snapshot, changed, stream.closed
def main():
events = (('open', 0), ('slice', 1, 0, 1, 4), ('readonly', 2, 1),
('snapshot', 2), ('write', 0, 2, 90), ('release', 0),
('read', 2), ('close',), ('release', 1), ('close',),
('release', 2), ('close',))
actual = run_schedule(b'ABCD', events)
expected_outcomes = ['ok'] * 7 + ['BufferError', 'ok', 'BufferError', 'ok', 'ok']
checks = {'manual_main_trace_values_and_lifetime_outcomes':
[row['outcome'] for row in actual] == expected_outcomes
and actual[3]['value'] == '424344' and actual[6]['value'] == '425a44'}
readonly = run_schedule(b'ab', (('open', 0), ('readonly', 1, 0),
('write', 1, 0, 88), ('write', 0, 0, 89), ('read', 1)))
checks['readonly_rejects_write_but_observes_other_path_mutation'] = (
readonly[2]['outcome'] == 'TypeError' and readonly[4]['value'] == '5962')
before, after, closed = snapshot_handoff()
checks['bytes_handoff_is_immutable_and_survives_exporter_close'] = (
type(before) is bytes and before == b'ABCD' and after == b'AZCD'
and closed and rejects(TypeError, assign_first, before))
with io.BytesIO(b'abcd') as stream:
with stream.getbuffer() as view:
view[1:3] = b'XY'
checks['same_length_view_assignment_is_not_exporter_resizing'] = (
view.tobytes() == b'aXYd' and rejects(ValueError, mismatched_assignment, view))
growth = run_schedule(b'a', (('open', 0), ('append', 90), ('read', 0)))
checks['growing_append_blocked_while_root_exported'] = (
growth[1]['outcome'] == 'BufferError' and growth[2]['value'] == '61')
closing = run_schedule(b'a', (('open', 0), ('close',), ('read', 0)))
checks['failed_close_does_not_close_exporter_or_invalidate_view'] = (
closing[1]['outcome'] == 'BufferError' and closing[2]['value'] == '61')
release = run_schedule(b'a', (('open', 0), ('release', 0), ('release', 0), ('read', 0)))
checks['release_is_repeatable_but_released_access_fails'] = (
[row['outcome'] for row in release] == ['ok', 'ok', 'ok', 'ValueError'])
child = run_schedule(b'ab', (('open', 0), ('slice', 1, 0, 1, 2),
('release', 0), ('read', 1), ('append', 88), ('close',)))
checks['retained_slice_survives_parent_release_and_blocks_resize_close'] = (
child[3]['value'] == '62' and child[4]['outcome'] == child[5]['outcome'] == 'BufferError')
empty_child = run_schedule(b'ab', (('open', 0), ('slice', 1, 0, 1, 1),
('release', 0), ('read', 1), ('close',), ('release', 1), ('close',)))
checks['even_empty_subview_retains_export_until_its_release'] = (
empty_child[3]['value'] == '' and empty_child[4]['outcome'] == 'BufferError'
and empty_child[-1]['outcome'] == 'ok')
last_readonly = run_schedule(b'a', (('open', 0), ('readonly', 1, 0),
('release', 0), ('close',), ('release', 1), ('append', 98),
('open', 2), ('read', 2), ('release', 2), ('close',)))
checks['last_readonly_export_release_allows_append_and_new_borrow'] = (
last_readonly[3]['outcome'] == 'BufferError'
and last_readonly[5]['outcome'] == 'ok' and last_readonly[7]['value'] == '6162'
and last_readonly[-1]['outcome'] == 'ok')
stream, retained = io.BytesIO(b'ab'), None
try:
with stream.getbuffer() as parent:
retained = parent[1:]
raise LookupError('synthetic body exception')
except LookupError:
context_result = (rejects(ValueError, bytes, parent)
and retained.tobytes() == b'b' and rejects(BufferError, stream.close))
finally:
if retained is not None:
retained.release()
stream.close()
checks['exceptional_with_releases_only_its_view_not_escaped_child'] = context_result and stream.closed
invalid = [(bytearray(b'a'), ()), (b'a', []), (b'x' * 17, ()), (b'', (('bad',),)),
(b'', (('open', True),)), (b'', (('open', 4),)), (b'', (('open', 0), ('open', 0))),
(b'', (('read', 0),)), (b'', (('append', 256),)), (b'', (('open', 0), ('write', 0, -1, 0))),
(b'', (('open', 0), ('slice', 1, 0, 2, 1))), (b'', (('close',),) * 13)]
checks['exact_types_declared_names_and_event_payload_caps_rejected'] = all(
rejects((TypeError, ValueError), run_schedule, payload, schedule) for payload, schedule in invalid)
endpoints = (run_schedule(b'', (('open', 0), ('read', 0), ('release', 0), ('append', 0), ('close',))),
run_schedule(bytes(range(16)), (('append', 1), ('open', 0), ('read', 0), ('release', 0), ('close',))))
fresh1 = run_schedule(b'a', (('open', 0), ('write', 0, 0, 90), ('read', 0)))
fresh2 = run_schedule(b'a', (('open', 0), ('read', 0)))
checks['empty_full_boundaries_and_fresh_scenarios_have_no_shared_state'] = (
endpoints[0][1]['value'] == '' and endpoints[0][3]['outcome'] == 'ok'
and endpoints[1][0]['outcome'] == 'ValueError'
and endpoints[1][2]['value'] == bytes(range(16)).hex()
and fresh1[-1]['value'] == '5a' and fresh2[-1]['value'] == '61')
cases, observations, oracle_ok = 0, 0, True
for length in range(5):
payload = bytes(range(length))
for begin in range(length + 1):
for end in range(begin, length + 1):
for value in (0, 255):
for order in itertools.permutations((0, 1, 2)):
prefix = (('open', 0), ('slice', 1, 0, begin, end), ('readonly', 2, 1))
edit = (('write', 0, 0, value),) if length else (('read', 0),)
finishing = tuple(event for name in order for event in (('release', name), ('close',)))
scenario = prefix + edit + finishing + (('read', 2), ('append', value))
observed = run_schedule(payload, scenario)
reference = independent_reference(payload, scenario)
oracle_ok = oracle_ok and observed == reference
cases += 1
observations += len(scenario)
checks['independent_scalar_lease_reference_420_schedules_ONE_group'] = (
oracle_ok and cases == 420 and observations == 5040
and actual == independent_reference(b'ABCD', events))
assert len(checks) == 14 and all(type(value) is bool and value for value in checks.values()), checks
return {'python': platform.python_version(), 'implementation': platform.python_implementation(),
'scope': 'Synthetic in-memory exact byte payload; registered views only; single-threaded; no RSS, allocation, speed, physical I/O or general-exporter claim.',
'limits': {'payload_bytes': 16, 'view_names': 4, 'events': 12},
'manual_payload_hex': '41424344', 'main_events': events, 'main_trace': actual,
'snapshot_handoff': {'before_hex': before.hex(), 'after_edit_hex': after.hex(), 'exporter_closed': closed},
'own_invalid_fixture_count': len(invalid), 'independent_schedules_ONE_group': cases,
'independent_event_observations_inside_same_group': observations,
'oracle_scope': 'Scalar list contents and independent per-view range/permission/active table; no io/memoryview or production calls.',
'check_groups': len(checks), 'checks': checks}
if __name__ == '__main__':
raw = (json.dumps(main(), ensure_ascii=True, indent=2, allow_nan=False) + '\n').encode('utf-8')
sys.stdout.buffer.write(raw)
مرجعِ مستقل، محتوای بایتها را در یک فهرستِ عددی و هر دسترسی را با آغاز، پایان، مجوز و وضعیتِ فعال نگه میدارد. نه BytesIO میسازد، نه memoryview و نه تابعِ اجراییِ اصلی را برای تولیدِ انتظار صدا میزند. این مرجع مدلِ همهٔ صادرکنندههای بافر نیست؛ برای همین عملیاتِ تعریفشده، انتظارِ محتوا و موفقیت یا نوعِ خطا را جدا محاسبه میکند. شاهدِ اصلی نیز پاسخهای دستیِ ثابت دارد.
خانوادهٔ محدود، طولهای صفر تا چهار، همهٔ برشهای مرتب، دو مقدارِ ویرایش و شش ترتیبِ آزادسازی را ترکیب میکند: ۴۲۰ سناریو و ۵۰۴۰ مشاهدهٔ رویداد داخلِ یک گروه. چهارده گروهِ نامدار، علاوه بر آن، انتساب، نسخهٔ مستقل، نمای تهی، خطای بدنه، تکرارِ release، ورودیِ نامعتبر و مرزِ شانزده بایت را بررسی میکنند. شمارِ حالتهای داخلی را گروهِ آزمونِ تازه نمینامیم.
{
"python": "3.14.5",
"implementation": "CPython",
"scope": "Synthetic in-memory exact byte payload; registered views only; single-threaded; no RSS, allocation, speed, physical I/O or general-exporter claim.",
"limits": {
"payload_bytes": 16,
"view_names": 4,
"events": 12
},
"manual_payload_hex": "41424344",
"main_events": [
[
"open",
0
],
[
"slice",
1,
0,
1,
4
],
[
"readonly",
2,
1
],
[
"snapshot",
2
],
[
"write",
0,
2,
90
],
[
"release",
0
],
[
"read",
2
],
[
"close"
],
[
"release",
1
],
[
"close"
],
[
"release",
2
],
[
"close"
]
],
"main_trace": [
{
"op": "open",
"outcome": "ok",
"value": null
},
{
"op": "slice",
"outcome": "ok",
"value": null
},
{
"op": "readonly",
"outcome": "ok",
"value": null
},
{
"op": "snapshot",
"outcome": "ok",
"value": "424344"
},
{
"op": "write",
"outcome": "ok",
"value": null
},
{
"op": "release",
"outcome": "ok",
"value": null
},
{
"op": "read",
"outcome": "ok",
"value": "425a44"
},
{
"op": "close",
"outcome": "BufferError",
"value": null
},
{
"op": "release",
"outcome": "ok",
"value": null
},
{
"op": "close",
"outcome": "BufferError",
"value": null
},
{
"op": "release",
"outcome": "ok",
"value": null
},
{
"op": "close",
"outcome": "ok",
"value": null
}
],
"snapshot_handoff": {
"before_hex": "41424344",
"after_edit_hex": "415a4344",
"exporter_closed": true
},
"own_invalid_fixture_count": 12,
"independent_schedules_ONE_group": 420,
"independent_event_observations_inside_same_group": 5040,
"oracle_scope": "Scalar list contents and independent per-view range/permission/active table; no io/memoryview or production calls.",
"check_groups": 14,
"checks": {
"manual_main_trace_values_and_lifetime_outcomes": true,
"readonly_rejects_write_but_observes_other_path_mutation": true,
"bytes_handoff_is_immutable_and_survives_exporter_close": true,
"same_length_view_assignment_is_not_exporter_resizing": true,
"growing_append_blocked_while_root_exported": true,
"failed_close_does_not_close_exporter_or_invalidate_view": true,
"release_is_repeatable_but_released_access_fails": true,
"retained_slice_survives_parent_release_and_blocks_resize_close": true,
"even_empty_subview_retains_export_until_its_release": true,
"last_readonly_export_release_allows_append_and_new_borrow": true,
"exceptional_with_releases_only_its_view_not_escaped_child": true,
"exact_types_declared_names_and_event_payload_caps_rejected": true,
"empty_full_boundaries_and_fresh_scenarios_have_no_shared_state": true,
"independent_scalar_lease_reference_420_schedules_ONE_group": true
}
}
خروجی با Python 3.14.5 ثبت شده و مستنداتِ خواندهشده نسخهٔ 3.14.8 هستند. منابع در ۲۹ مهر ۱۴۰۵ بررسی شدند؛ جستوجوی هفتروزه و سپس سیروزه، تغییرِ تازهٔ مرتبطی را اثبات نکرد، پس این آموزشِ پایه است. آزمونِ زمان، قلهٔ تخصیص و آزادسازیِ فیزیکی نداریم. موفقیتِ این خانواده فقط همین قرارداد، ورودیها و محیطِ ثبتشده را پوشش میدهد؛ فراخوانیِ خارجی یا نماهای فراریِ ثبتنشده در مدل نیستند.
۵. چهار پرسش برای تحویلِ امنِ داده

پرسش اول: نمای فقطخواندنیِ دو چرا BZD را میبیند؟ پاسخ: این نما هنوز به همان محتوای مشترک نگاه میکند. مجوزِ نوشتن از مسیرِ خودش حذف شده، اما نمای صفر قبلاً خانهٔ مشترک را تغییر داده است. برای نگهداشتنِ مقدارِ پیشین، نسخهٔ مستقل باید پیش از آن ویرایش ساخته شود.
پرسش دوم: پس از release والد، چرا close هنوز خطا میدهد؟ پاسخ: نمای مشتقِ نگهداشتهشده هنوز زنده است. در شاهد، حتی آزادسازیِ برشِ یک کافی نیست، چون نمای دو باقی مانده است. دفترِ مالکیت باید پایانِ هر دسترسی را روشن کند؛ نامِ «فرزند» به معنیِ آزادسازیِ خودکار همراهِ والد نیست.
پرسش سوم: آیا with والد، برشِ بیرونرفته را پاک میکند؟ پاسخ: خیر؛ خروج فقط همان نما را آزاد میکند. یا برش باید در محدودهٔ کوتاهترِ خودش آزاد شود، یا برای تحویلِ داده، bytes مستقل ساخته و تمامِ نماها بسته شوند. این مثال، روشِ لغوِ دسترسیِ دلخواهِ مصرفکنندهٔ خارجی نیست.
پرسش چهارم: برشِ تهی صفر بایت دارد؛ پس چرا پایانِ عمر مهم است؟ پاسخ: تعدادِ بایتِ قابلخواندن با وجودِ دسترسیِ صادرشده یکسان نیست. شاهدِ تهی همچنان مانعِ close میشود تا release شود. از صفر بودنِ طول نمیتوان نبودِ وابستگی، آزادشدنِ حافظه یا بهترشدنِ کارایی را نتیجه گرفت.
منابع و مطالعهٔ بیشتر
بررسی منابع: ۲۹ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

