Li que C não exige um tamanho mínimo de pilha, deixando-o definido pela implementação. Mas um compilador C em conformidade poderia se recusar a compilar um programa se ele detectar — digamos, por meio de análise estática — que a profundidade da pilha de chamadas (por exemplo, de uma chamada de função aninhada) excede um limite fixo que ele suporta? Ou isso é sempre um problema de tempo de execução? Estou curioso para saber se o padrão C (como C11 ou C23) permite rejeitar sintaxe válida em tempo de compilação com base apenas na profundidade da pilha, assumindo que não há recursão ou comportamento indefinido
Considere uma classe base com alguns campos públicos e uma classe derivada que herda a base privadamente e torna um dos campos herdados público por meio da using
declaração. Preciso acessar esse campo público da classe derivada com um ponteiro de membro. Aqui está um código:
#include <iostream>
class Base
{
public:
int derived_public_field = 1;
int derived_private_field = 1;
};
class Derived : private Base
{
public:
using Base::derived_public_field;
};
int main() {
Derived d;
int Derived::* member_ptr = &Derived::derived_public_field;
d.*member_ptr = 2;
std::cout << d.derived_public_field << '\n';
}
Isso não funciona, pois a &Derived::derived_public_field
expressão faz com que o compilador (tentei Clang e GCC) pense que estou tentando acessar uma base inacessível. GCC diz:
error: 'Base' is an inaccessible base of 'Derived'
Eu também tentei isso:
int Base::* member_ptr = &Base::derived_public_field;
o que fez com que o mesmo erro aparecesse mais tarde, quando tentei escrever um valor por meio do ponteiro.
Existe uma maneira de fazer o compilador ver que derived_public_field
é realmente acessível?
Estou usando C++ para um projeto de firmware de baixo nível para um microcontrolador. Tudo estava bem por um tempo, com o tamanho do código mantido muito pequeno, até que introduzi esta linha de código:
uint8_t index = __builtin_ctz(exti_set_mask);
auto &gpio_irq_info = irq_callback_table[index];
O problema aqui é que irq_callback_table
é um std::array<>
de tamanho 16. exti_set_mask
É um uint16_t
que provavelmente está sendo promovido para uma uint32_t
(arquitetura de CPU de 32 bits), então o compilador não pode ter certeza de que o resultado de __builtin_ctz()
retorna algo menor que 16. Com o potencial de um acesso fora dos limites, o tamanho do código do programa explode por causa de todo o código adicionado para dar suporte ao lançamento de uma exceção.
Mascarar o resultado antes de usá-lo como índice elimina todo esse código de exceção:
uint8_t index = __builtin_ctz(exti_set_mask) & 0xf;
auto &gpio_irq_info = irq_callback_table[index];
O que reduz o tamanho do código do programa para cerca de 4 kB contra 90 kB!
Tenho essas bandeiras definidas via meson
:
# Specify global compiler flags.
add_project_arguments(
# Free standing environment (no OS, or stdlib)
'-ffreestanding', '-nostdlib', '-specs=nosys.specs',
# Enable linker garbage collection
'-ffunction-sections', '-fdata-sections',
# No dynamic memory allocation
'-fno-builtin-malloc', '-fno-builtin-calloc',
'-fno-builtin-realloc', '-fno-builtin-free',
# Debug optimization
'-Og',
language: ['cpp', 'c']
)
add_project_arguments(
'-std=c++20',
'-fno-exceptions',
'-fno-rtti',
language: ['cpp']
)
Mas o -fno-exceptions
sinalizador não parece ser suficiente para evitar a emissão de código de exceção. Idealmente, eu gostaria que qualquer emissão potencial de exceções (ou malloc, etc.) aparecesse como um aviso ou erro. Alguma ideia de como fazer isso?
EDIT: Para aqueles que desejam comparar a saída do arquivo .elf objdump, aqui está o .elf totalmente vinculado com o índice mascarado: https://pastebin.com/hSJgF3PW
E o .elf com o índice não mascarado:
- https://pastebin.com/p9yDxdjx pt1
- https://pastebin.com/NU5nV0tr pt2
- https://pastebin.com/9CX1EtH0 pt3
- https://pastebin.com/evvnmCCa pt4
A função onde os dois se desviam:
Índice não mascarado:
08000d1c <(anonymous namespace)::CommonISRHandler(unsigned short)>:
void CommonISRHandler([[maybe_unused]] uint16_t exti_set_mask) {
8000d1c: b570 push {r4, r5, r6, lr}
8000d1e: 0005 movs r5, r0
while (exti_set_mask) {
8000d20: e011 b.n 8000d46 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x2a>
// Element access.
[[__nodiscard__]]
_GLIBCXX17_CONSTEXPR reference
operator[](size_type __n) noexcept
{
__glibcxx_requires_subscript(__n);
8000d22: 4b18 ldr r3, [pc, #96] @ (8000d84 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x68>)
8000d24: 4a18 ldr r2, [pc, #96] @ (8000d88 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x6c>)
8000d26: 4819 ldr r0, [pc, #100] @ (8000d8c <(anonymous namespace)::CommonISRHandler(unsigned short)+0x70>)
8000d28: 21ca movs r1, #202 @ 0xca
8000d2a: f000 f97d bl 8001028 <std::__glibcxx_assert_fail(char const*, int, char const*, char const*)>
EXTI->RPR1 &= (1 << index);
8000d2e: 4a18 ldr r2, [pc, #96] @ (8000d90 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x74>)
8000d30: 68d1 ldr r1, [r2, #12]
8000d32: 2301 movs r3, #1
8000d34: 40a3 lsls r3, r4
8000d36: 4019 ands r1, r3
8000d38: 60d1 str r1, [r2, #12]
EXTI->FPR1 &= (1 << index);
8000d3a: 6911 ldr r1, [r2, #16]
8000d3c: 4019 ands r1, r3
8000d3e: 6111 str r1, [r2, #16]
exti_set_mask &= ~(1 << index);
8000d40: 43db mvns r3, r3
8000d42: b21b sxth r3, r3
8000d44: 401d ands r5, r3
while (exti_set_mask) {
8000d46: 2d00 cmp r5, #0
8000d48: d01a beq.n 8000d80 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x64>
uint8_t index = __builtin_ctz(exti_set_mask);
8000d4a: 0028 movs r0, r5
8000d4c: f7ff fa40 bl 80001d0 <__ctzsi2>
auto &gpio_irq_info = irq_callback_table[index];
8000d50: 24ff movs r4, #255 @ 0xff
8000d52: 4004 ands r4, r0
8000d54: 2c0f cmp r4, #15
8000d56: d8e4 bhi.n 8000d22 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x6>
return stub_ptr != nullptr;
8000d58: 4b0e ldr r3, [pc, #56] @ (8000d94 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x78>)
8000d5a: 0062 lsls r2, r4, #1
8000d5c: 1912 adds r2, r2, r4
8000d5e: 0092 lsls r2, r2, #2
8000d60: 189b adds r3, r3, r2
8000d62: 689b ldr r3, [r3, #8]
if (gpio_irq_info.callback.IsValid()) {
8000d64: 2b00 cmp r3, #0
8000d66: d0e2 beq.n 8000d2e <(anonymous namespace)::CommonISRHandler(unsigned short)+0x12>
gpio_irq_info.callback(gpio_irq_info.pin);
8000d68: 4a0a ldr r2, [pc, #40] @ (8000d94 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x78>)
8000d6a: 0060 lsls r0, r4, #1
8000d6c: 1901 adds r1, r0, r4
8000d6e: 0089 lsls r1, r1, #2
8000d70: 5c89 ldrb r1, [r1, r2]
8000d72: 1900 adds r0, r0, r4
8000d74: 0080 lsls r0, r0, #2
8000d76: 1880 adds r0, r0, r2
8000d78: 3004 adds r0, #4
8000d7a: f7ff ffbb bl 8000cf4 <rtlib::Delegate<void (mcu::stm32g070::Gpio::GpioId)>::operator()(mcu::stm32g070::Gpio::GpioId) const>
8000d7e: e7d6 b.n 8000d2e <(anonymous namespace)::CommonISRHandler(unsigned short)+0x12>
}
8000d80: bd70 pop {r4, r5, r6, pc}
8000d82: 46c0 nop @ (mov r8, r8)
8000d84: 08013ccc .word 0x08013ccc
8000d88: 08013ce0 .word 0x08013ce0
8000d8c: 08013dc4 .word 0x08013dc4
8000d90: 40021800 .word 0x40021800
8000d94: 20000734 .word 0x20000734
Índice mascarado:
08000710 <(anonymous namespace)::CommonISRHandler(unsigned short)>:
void CommonISRHandler([[maybe_unused]] uint16_t exti_set_mask) {
8000710: b570 push {r4, r5, r6, lr}
8000712: 0005 movs r5, r0
while (exti_set_mask) {
8000714: e00b b.n 800072e <(anonymous namespace)::CommonISRHandler(unsigned short)+0x1e>
EXTI->RPR1 &= (1 << index);
8000716: 4a14 ldr r2, [pc, #80] @ (8000768 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x58>)
8000718: 68d1 ldr r1, [r2, #12]
800071a: 2301 movs r3, #1
800071c: 40a3 lsls r3, r4
800071e: 4019 ands r1, r3
8000720: 60d1 str r1, [r2, #12]
EXTI->FPR1 &= (1 << index);
8000722: 6911 ldr r1, [r2, #16]
8000724: 4019 ands r1, r3
8000726: 6111 str r1, [r2, #16]
exti_set_mask &= ~(1 << index);
8000728: 43db mvns r3, r3
800072a: b21b sxth r3, r3
800072c: 401d ands r5, r3
while (exti_set_mask) {
800072e: 2d00 cmp r5, #0
8000730: d018 beq.n 8000764 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x54>
uint8_t index = __builtin_ctz(exti_set_mask) & 0xf;
8000732: 0028 movs r0, r5
8000734: f7ff fcc0 bl 80000b8 <__ctzsi2>
8000738: 240f movs r4, #15
800073a: 4004 ands r4, r0
return stub_ptr != nullptr;
800073c: 4b0b ldr r3, [pc, #44] @ (800076c <(anonymous namespace)::CommonISRHandler(unsigned short)+0x5c>)
800073e: 0062 lsls r2, r4, #1
8000740: 1912 adds r2, r2, r4
8000742: 0092 lsls r2, r2, #2
8000744: 189b adds r3, r3, r2
8000746: 689b ldr r3, [r3, #8]
if (gpio_irq_info.callback.IsValid()) {
8000748: 2b00 cmp r3, #0
800074a: d0e4 beq.n 8000716 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x6>
gpio_irq_info.callback(gpio_irq_info.pin);
800074c: 4a07 ldr r2, [pc, #28] @ (800076c <(anonymous namespace)::CommonISRHandler(unsigned short)+0x5c>)
800074e: 0060 lsls r0, r4, #1
8000750: 1901 adds r1, r0, r4
8000752: 0089 lsls r1, r1, #2
8000754: 5c89 ldrb r1, [r1, r2]
8000756: 1900 adds r0, r0, r4
8000758: 0080 lsls r0, r0, #2
800075a: 1880 adds r0, r0, r2
800075c: 3004 adds r0, #4
800075e: f7ff ffc3 bl 80006e8 <rtlib::Delegate<void (mcu::stm32g070::Gpio::GpioId)>::operator()(mcu::stm32g070::Gpio::GpioId) const>
8000762: e7d8 b.n 8000716 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x6>
}
8000764: bd70 pop {r4, r5, r6, pc}
8000766: 46c0 nop @ (mov r8, r8)
8000768: 40021800 .word 0x40021800
800076c: 2000005c .word 0x2000005c
EDIT #2 , Alguns bons comentários até agora, obrigado. Minha razão para acreditar que exceções estão sendo puxadas é por causa da presença de muitas funções no objdump não mascarado que não estão presentes na saída do objdump mascarado ( grep -i exception
, grep -i throw
):
08000438 <_Unwind_RaiseException>:
800045e: f009 f831 bl 80094c4 <__gnu_Unwind_RaiseException>
8001764: f007 fb0e bl 8008d84 <__cxa_current_exception_type>
080089d8 <__cxxabiv1::__is_gxx_exception_class(char*)>:
80089e0: d000 beq.n 80089e4 <__cxxabiv1::__is_gxx_exception_class(char*)+0xc>
80089e8: d1fb bne.n 80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
80089ee: d1f8 bne.n 80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
80089f4: d1f5 bne.n 80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
80089fa: d1f2 bne.n 80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
8008a00: d1ef bne.n 80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
8008a06: d1ec bne.n 80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
8008a12: e7e6 b.n 80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
8008a20: f7ff ffda bl 80089d8 <__cxxabiv1::__is_gxx_exception_class(char*)>
8008a44: f7ff ffc8 bl 80089d8 <__cxxabiv1::__is_gxx_exception_class(char*)>
8008b02: f000 fad3 bl 80090ac <__cxa_allocate_exception>
8008bc0: f000 fce6 bl 8009590 <_Unwind_DeleteException>
8008c2c: f000 fcb0 bl 8009590 <_Unwind_DeleteException>
08008c34 <std::bad_exception::~bad_exception()>:
08008c38 <transaction clone for std::bad_exception::what() const>:
8008c38: 4800 ldr r0, [pc, #0] @ (8008c3c <transaction clone for std::bad_exception::what() const+0x4>)
08008c40 <std::bad_exception::~bad_exception()>:
08008c60 <__gxx_exception_cleanup(_Unwind_Reason_Code, _Unwind_Control_Block*)>:
8008c64: d812 bhi.n 8008c8c <__gxx_exception_cleanup(_Unwind_Reason_Code, _Unwind_Control_Block*)+0x2c>
8008c72: d000 beq.n 8008c76 <__gxx_exception_cleanup(_Unwind_Reason_Code, _Unwind_Control_Block*)+0x16>
8008c7e: d001 beq.n 8008c84 <__gxx_exception_cleanup(_Unwind_Reason_Code, _Unwind_Control_Block*)+0x24>
8008c86: f000 fa27 bl 80090d8 <__cxa_free_exception>
8008c8a: e7f3 b.n 8008c74 <__gxx_exception_cleanup(_Unwind_Reason_Code, _Unwind_Control_Block*)+0x14>
08008c94 <__cxa_init_primary_exception>:
8008cd0: 4b02 ldr r3, [pc, #8] @ (8008cdc <__cxa_init_primary_exception+0x48>)
8008cf8: f7ff ffcc bl 8008c94 <__cxa_init_primary_exception>
8008d06: f7f7 fb97 bl 8000438 <_Unwind_RaiseException>
08008d84 <__cxa_current_exception_type>:
8008d8e: d004 beq.n 8008d9a <__cxa_current_exception_type+0x16>
8008d96: d001 beq.n 8008d9c <__cxa_current_exception_type+0x18>
8008da0: e7fa b.n 8008d98 <__cxa_current_exception_type+0x14>
080090ac <__cxa_allocate_exception>:
80090b8: d007 beq.n 80090ca <__cxa_allocate_exception+0x1e>
80090d2: d1f2 bne.n 80090ba <__cxa_allocate_exception+0xe>
080090d8 <__cxa_free_exception>:
80090d8: 4b07 ldr r3, [pc, #28] @ (80090f8 <__cxa_free_exception+0x20>)
80090e6: d201 bcs.n 80090ec <__cxa_free_exception+0x14>
80090ea: d302 bcc.n 80090f2 <__cxa_free_exception+0x1a>
80090f6: e7fb b.n 80090f0 <__cxa_free_exception+0x18>
080094c4 <__gnu_Unwind_RaiseException>:
80094e0: e006 b.n 80094f0 <__gnu_Unwind_RaiseException+0x2c>
80094ee: d108 bne.n 8009502 <__gnu_Unwind_RaiseException+0x3e>
80094fa: d0f2 beq.n 80094e2 <__gnu_Unwind_RaiseException+0x1e>
800950a: d1f7 bne.n 80094fc <__gnu_Unwind_RaiseException+0x38>
8009584: f7ff ff9e bl 80094c4 <__gnu_Unwind_RaiseException>
08009590 <_Unwind_DeleteException>:
8009598: d001 beq.n 800959e <_Unwind_DeleteException+0xe>
08000498 <_Unwind_Resume_or_Rethrow>:
80004be: f009 f857 bl 8009570 <__gnu_Unwind_Resume_or_Rethrow>
80017c6: f007 faa5 bl 8008d14 <__cxa_rethrow>
8008ad0: f000 f920 bl 8008d14 <__cxa_rethrow>
8008b0e: f000 f8e7 bl 8008ce0 <__cxa_throw>
08008ce0 <__cxa_throw>:
08008d14 <__cxa_rethrow>:
8008d24: d00c beq.n 8008d40 <__cxa_rethrow+0x2c>
8008d2c: d00a beq.n 8008d44 <__cxa_rethrow+0x30>
8008d36: f7f7 fbaf bl 8000498 <_Unwind_Resume_or_Rethrow>
8008d4a: d1f0 bne.n 8008d2e <__cxa_rethrow+0x1a>
8008d52: d1ec bne.n 8008d2e <__cxa_rethrow+0x1a>
8008d5a: d1e8 bne.n 8008d2e <__cxa_rethrow+0x1a>
8008d62: d1e4 bne.n 8008d2e <__cxa_rethrow+0x1a>
8008d6a: d1e0 bne.n 8008d2e <__cxa_rethrow+0x1a>
8008d72: d1dc bne.n 8008d2e <__cxa_rethrow+0x1a>
8008d7a: d8d8 bhi.n 8008d2e <__cxa_rethrow+0x1a>
8008d82: e7d6 b.n 8008d32 <__cxa_rethrow+0x1e>
08009570 <__gnu_Unwind_Resume_or_Rethrow>:
8009576: d005 beq.n 8009584 <__gnu_Unwind_Resume_or_Rethrow+0x14>
8009588: e7fb b.n 8009582 <__gnu_Unwind_Resume_or_Rethrow+0x12>
Associações estruturadas não permitem tipos decomponíveis vazios.
auto [] = std::make_tuple(); // error
Desde que P1061R10 foi aceito para C++26, isso permite que ligações estruturadas introduzam pacotes (desde que o pacote seja declarado dentro do contexto do modelo):
auto [...args] = return_empty_tuple();
auto [one, ...rest] = return_single_tuple();
Este último permite ...rest
ser um pacote vazio se return_single_tuple()
tiver um tamanho de ligação estruturado de 1.
Minha pergunta é: ...args
ainda é possível declarar um pacote vazio mesmo que return_empty_tuple()
tenha um tamanho de ligação estruturada zero (por exemplo, tamanho de tupla zero)?
Na unidade Vcl.Buttons do Delphi eles chamam:
Caption := LoadResString(BitBtnCaptions[Value]);
Onde BitnBtnCaptions
está uma matriz como:
BitnBtnCaptions: array[TBitBtnKind] of Pointer = (
nil, @SOKButton, @SCancelButton, @SHelpButton, @SYesButton, @SNoButton,
@SCloseButton, @SAbortButton, @SRetryButton, @SIgnoreButton,
@SAllButton);
E as BitnBtnCaptions
constantes são:
resourcestring
SOKButton = 'OK';
SCancelButton = 'Cancel';
SYesButton = '&Yes';
SNoButton = '&No';
SHelpButton = '&Help';
SCloseButton = '&Close';
SIgnoreButton = '&Ignore';
SRetryButton = '&Retry';
SAbortButton = 'Abort';
SAllButton = '&All';
Então, basicamente, ele está chamando:
resourcestring
SOKButton = 'OK';
s := LoadResString(@SOKButton);
A declaração de LoadResString é:
function LoadResString(ResStringRec: PResStringRec): string;
Esta função requer um ponteiro para um TResStringRec :
PResStringRec = ^TResStringRec;
TResStringRec = packed record
// 32bit = 8 bytes
// 64bit = 16 bytes
Module: ^HMODULE;
Identifier: NativeUint;
end;
Mas estamos passando uma resourcestring .
Isso significa que estamos passando uma string para uma função que só aceita um PResStringRec em caso de erro?
Por que você pergunta?
Pergunto porque estou chamando:
Result := LoadResString(@SMsgDlgOK);
e com avaliação de ponteiro digitado ( ie {$T+}
ou {$TYPEDADDRESS ON}
) ele emite um aviso de tipo incompatível:
E2010 Tipos incompatíveis: 'PResStringRec' e 'Pointer'
E é claro que eu posso simplesmente forçar com um elenco forte:
Result := LoadResString(PResStringRec(@SMsgDlgOK));
Elenco difícil?
Mas isso parece um pouco duro para algo que supostamente é a maneira correta de fazer algo. Cheira um pouco estranho.
E o pior é que se estou fazendo um gesso forte , é melhor saber o que estou fazendo.
E do jeito que eu vejo, a única maneira de isso funcionar é se:
- a constante mágica
SMsgDlgOk
- na verdade é um
TResStringRec
.
Não queremos forçar uma peça triangular no furo quadrado , usando um molde rígido às cegas.
O que me leva à minha pergunta: uma resourcestring
resourcestring
SMsgDlgOK = 'OK';
uma string? Ou é um registro?
Mas eu realmente tenho que fazer isso?
Eu preciso realmente fazer o que ele Vcl.Buttons
faz? Vcl.Buttons
Precisa fazer o que ele está fazendo?
Não podemos simplesmente substituir:
s := LoadResString(@SOKButton);
com
s := SOKButton
Não era esse o ponto principal da palavra-chave resourcestring ? Ela coloca as strings na tabela strings (onde os localizadores podem localizá-las) e faz toda a mágica em tempo de execução (ou seja, LoadResString ) para expor as resourcestrings como strings ?
E se isso não for verdade: por que não?
- O que é resourcestring?
- e como ele difere de LoadResString(resourceString)?
O que ganho ligando para:
LoadResString(@SOKButton)
sobre apenas usar SOKButton
?
E se eu tiver que usar LoadResString , é realmente , realmente , verdadeiramente , juro, 100% seguro forçar o typecast?
- Ideal :
s := SOKButton
- Atual :
s := LoadResString(@SOKButton)
// falha na verificação de ponteiro digitado - Correto (?) :
s := LoadResString(PResStringRec(@SOKButton))
Bate-papo bônus
Se um resourcestring realmente for um TResStringRect
, então eu deveria ser capaz de vê-lo. Então eu inspeciono o que eles são:
╔════════════════════════════╤══════════╗
║ Watch Name │ Value ║
╠════════════════════════════╪══════════╣
║ PResStringRec(@SMsgDlgOK) │ $AD3C54 ║
║ ├──Module │ $400000 ║
║ ╰──Identifier │ 0 ║
╟┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┼┈┈┈┈┈┈┈┈┈┈╢
║ PResStringRec(@SMsgDlgYes) │ $AD3C54 ║
║ ├──Module │ $400000 ║
║ ╰──Identifier │ 0 ║
╟┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┼┈┈┈┈┈┈┈┈┈┈╢
║ PResStringRec(@SMsgDlgNo) │ $AD3C54 ║
║ ├──Module │ $400000 ║
║ ╰──Identifier │ 0 ║
╚════════════════════════════╧══════════╝
Cada sequência de recursos:
- mora no mesmo endereço
- tem o mesmo módulo
- tem o mesmo identificador
Então algo não está certo; não parece um disco para mim.
Então por que o código funciona Vcl.Buttons
?
Tenho um caso em que o tipo struct não precisa ser padrão operator<=>
- só que nem todos os campos são importantes ao definir a ordem dos objetos.
struct A
{
int a;
int b; // not important when ordering
int c;
constexpr std::strong_ordering operator<=>(const A& rhs) const
{
if (const auto res = a<=>rhs.a; res != 0) return res;
return c<=>rhs.c;
}
};
Para minha surpresa, não posso usar container com esse tipo como argumento para std::ranges::sort
:
std::array<A, 100> aa{};
std::ranges::sort(aa);
Eu verifiquei no mais novo gcc (14) e clang (19) e ele basicamente afirma que o intervalo não é classificável porque std::invocable_v<std::ranges::less&, A&, A&>
não é verdade. Veja o link godbold
Mais observações:
- Funciona com std::sort(
std::sort(aa.begin(), aa.end());
) - Funciona ao adicionar não padrão
operator==
- E funciona com o padrão (
=default
)operator<=>
Isso é um bug do compilador, um bug padrão do C++ ou alguma regra estranha do C++ está em jogo aqui e deveria ser assim? Se a última opção estiver correta - você pode fornecer uma justificativa para tal comportamento?
Atualizar:
Pelos comentários parece que std::ranges::less{}(A{}, A{})
requer não apenas operator<
(derivado de operator<=>
), mas também operator==
que não pode ser derivado de não padrão, operator<=>
então o erro.
Mas uma questão permanece: por que std::ranges::less
as necessidades operator==
?
E melhor exemplo dos comentários: link
Dada a seguinte função
void foo(float* result, int size, float y, float delta) {
for (int t = 0; t < size; ++t) {
result[t] = y + delta * t;
}
}
O Clang -O2
gera o seguinte assembly x86-64 :
.LCPI0_0:
.long 0
.long 1
.long 2
.long 3
.LCPI0_1:
.long 4
.long 4
.long 4
.long 4
.LCPI0_2:
.long 65535
.long 65535
.long 65535
.long 65535
.LCPI0_3:
.long 1258291200
.long 1258291200
.long 1258291200
.long 1258291200
.LCPI0_4:
.long 1392508928
.long 1392508928
.long 1392508928
.long 1392508928
.LCPI0_5:
.long 0x53000080
.long 0x53000080
.long 0x53000080
.long 0x53000080
.LCPI0_6:
.long 8
.long 8
.long 8
.long 8
foo(float*, int, float, float):
test esi, esi
jle .LBB0_7
mov eax, esi
cmp esi, 7
ja .LBB0_3
xor ecx, ecx
jmp .LBB0_6
.LBB0_3:
mov ecx, eax
and ecx, 2147483640
movaps xmm2, xmm1
shufps xmm2, xmm1, 0
movaps xmm3, xmm0
shufps xmm3, xmm0, 0
mov edx, eax
shr edx, 3
and edx, 268435455
shl rdx, 5
movdqa xmm4, xmmword ptr [rip + .LCPI0_0]
xor esi, esi
movdqa xmm5, xmmword ptr [rip + .LCPI0_1]
movdqa xmm6, xmmword ptr [rip + .LCPI0_2]
movdqa xmm7, xmmword ptr [rip + .LCPI0_3]
movdqa xmm8, xmmword ptr [rip + .LCPI0_4]
movaps xmm9, xmmword ptr [rip + .LCPI0_5]
movdqa xmm10, xmmword ptr [rip + .LCPI0_6]
.LBB0_4:
movdqa xmm11, xmm4
paddd xmm11, xmm5
movdqa xmm12, xmm4
pand xmm12, xmm6
por xmm12, xmm7
movdqa xmm13, xmm4
psrld xmm13, 16
por xmm13, xmm8
subps xmm13, xmm9
addps xmm13, xmm12
movdqa xmm12, xmm11
pand xmm12, xmm6
por xmm12, xmm7
psrld xmm11, 16
por xmm11, xmm8
subps xmm11, xmm9
addps xmm11, xmm12
mulps xmm13, xmm2
addps xmm13, xmm3
mulps xmm11, xmm2
addps xmm11, xmm3
movups xmmword ptr [rdi + rsi], xmm13
movups xmmword ptr [rdi + rsi + 16], xmm11
paddd xmm4, xmm10
add rsi, 32
cmp rdx, rsi
jne .LBB0_4
cmp ecx, eax
je .LBB0_7
.LBB0_6:
xorps xmm2, xmm2
cvtsi2ss xmm2, ecx
mulss xmm2, xmm1
addss xmm2, xmm0
movss dword ptr [rdi + 4*rcx], xmm2
inc rcx
cmp rax, rcx
jne .LBB0_6
.LBB0_7:
ret
Estou tentando entender o que está acontecendo aqui. Parece que .LBB0_4
é um loop que cobre 8 iterações do loop original para cada iteração (há 2 mulps
instruções e cada instrução cobre 4 float
s e rsi
é incrementada em 32). O código no final provavelmente está lá para cobrir o caso em que size
não é divisível por 8. O que estou tendo problemas é o resto do código. O que todas essas outras instruções dentro do .LBB0_4
loop e as constantes no início estão fazendo? Existe uma ferramenta ou um argumento do compilador que pode me ajudar a entender o resultado da vetorização SIMD? Talvez algo que transforme isso de volta em C++ com intrínsecos SIMD?
Também se eu mudar o código para isso
void foo(float* result, int size, float y, float delta) {
for (int t = 0; t < size; ++t) {
result[t] = y;
y += delta;
}
}
O Clang gera uma montagem muito menor e faz um loop em 16 valores de uma só vez .
Edição: Acabei de perceber que esta versão não é vetorizada e, portanto, é menor e provavelmente mais lenta.
Qual é a maneira mais rápida de escrever este código?
Problema de alto nível: Quero atualizar uma tabela existente para preencher uma coluna existente com 32 bytes gerados aleatoriamente, codificados em base64. Os dados aleatórios devem ser diferentes para cada linha.
Ignorando por um momento o requisito de codificação base64, a solução é simples, conforme ilustrado neste código de exemplo:
DECLARE @table TABLE (
id int,
bin varbinary(max) null
)
-- put a few rows in the table
insert into @table (id) values (1)
insert into @table (id) values (2)
insert into @table (id) values (3)
-- perform the update
update @table
set bin = CRYPT_GEN_RANDOM(32)
-- check result
select *
from @table
Isso funciona como esperado. CRYPT_GEN_RANDOM(32)
gera um valor diferente para cada linha atualizada. Agora tente adicionar o requisito de codificação base64:
DECLARE @table TABLE (
id int,
txt nvarchar(max) null
)
-- put a few rows in the table
insert into @table (id) values (1)
insert into @table (id) values (2)
insert into @table (id) values (3)
-- perform the update
update @table
set txt = (SELECT CRYPT_GEN_RANDOM(32) FOR XML PATH(''), BINARY BASE64)
-- check result
select *
from @table
Isso não funciona: ele coloca o mesmo valor em cada linha. Eu tentei empacotar a codificação base64 em um UDF, para ver se isso ajudaria:
CREATE FUNCTION ConvertBytesToBase64
(
@bytes varbinary(max)
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE @result nvarchar(max)
SET @result = (SELECT @bytes FOR XML PATH(''), BINARY BASE64)
RETURN @result
END
GO
E então a declaração de atualização se torna:
update @table
set txt = ConvertBytesToBase64(CRYPT_GEN_RANDOM(32))
Mas isso ainda produz o mesmo valor em todas as linhas.
O que eu fundamentalmente não entendo é, dado que o SQL Server avalia CRYPT_GEN_RANDOM(32)
para cada linha (o que parece sensato), por que ele não avalia ConvertBytesToBase64(CRYPT_GEN_RANDOM(32))
para cada linha? Como posso fazer com que ele avalie para cada linha? (e talvez relacionado, há uma maneira melhor de fazer codificação base64 no SQL Server 2019+?)
Eu estava escrevendo uma classe em javafx onde eu tinha duas propriedades que eram vinculadas bidirecionalmente e estava testando alguns casos extremos. Descobri que se você alterasse o valor de uma das propriedades de dentro de um ouvinte de invalidação, isso fazia com que as propriedades ficassem fora de sincronia. Aqui está um pequeno exemplo:
static class A {
IntegerProperty x = new SimpleIntegerProperty(this, "x", 0);
IntegerProperty y = new SimpleIntegerProperty(this, "y", 0);
A() {
x.bindBidirectional(y);
}
}
static void testA() {
A a = new A();
a.x.addListener( o -> { //InvalidationListener
if (a.x.get() < 0) a.x.set(0);
});
// after y is set to a negative value, x and y hold different values
// until either y is set to a value that's >= 0 or x is set to any value
a.y.set(-2);
System.out.println(a.x.get());
System.out.println(a.y.get());
}
Saída:
0
-2
Eu estava assumindo que ao usar uma ligação bidirecional, alterar uma propriedade sempre faria com que a outra fosse atualizada. Parece raro (e possivelmente imprudente) que alguém escrevesse um ouvinte de invalidação como esse, mas estou pensando defensivamente aqui. Se pelo menos uma dessas propriedades fosse exposta, não quero que seja possível quebrar nenhuma invariante da minha classe. Eu estava pensando que há três explicações possíveis aqui:
O contrato em ligações bidirecionais não é que elas sempre estejam em sincronia (ou elas mantêm o mesmo valor ou são marcadas como inválidas), é apenas em uma base de melhor esforço. Assim, invariantes de classe não devem ser baseadas nesse fato.
Alterar o valor dentro de um ouvinte de invalidação quebra a pré-condição de vínculos bidirecionais e deve ser evitado. Caso contrário, eles estão sempre em sincronia. Assim, você pode basear uma invariante de classe nesse fato, já que é razoável exigir que os clientes não escrevam ouvintes de invalidação assim.
É um bug e eles realmente devem ser sincronizados, não importa o que aconteça. Acho que se isso fosse verdade, você poderia facilmente produzir um loop infinito de notificações de alteração (como adicionar um ouvinte de invalidação a y que sempre define o valor como < 0). Então, caberia ao cliente evitar tais casos.
Alguma dessas explicações está próxima da verdade ou estou esquecendo de algo mais aqui? Além disso, eu gostaria de saber se existe algum outro tipo de operação bind que leve esses tipos de situações em consideração.
Entendo que std::is_constant_evaluated()
foi útil determinar a avaliação do tempo de compilação em C++20.
Mas desde C++23 temos if consteval
.
Entretanto, não há menção de descontinuação para std::is_constant_evaluated()
.
Existe algum uso prático para isso a partir do C++23?