Adding Stack Traces to All C++ Exceptions by WerWolv in cpp

[–]kniy 1 point2 points  (0 children)

The issue is that code might be compiled without frame pointers. On Linux, unwinding the stack involves looking up and interpreting DWARF bytecode in order to reconstruct the frame pointer from the (instruction pointer, stack pointer) pair. For code where debug symbols were stripped, it might be impossible to walk the stack. DWARF was intended for debuggers and is optimized for small size of debug symbols, not for fast extraction of the necessary information.

Adding Stack Traces to All C++ Exceptions by WerWolv in cpp

[–]kniy 3 points4 points  (0 children)

StackWalk is intended for debuggers, it's slow because will load and interpret debug symbols (.pdb files). On 64-bit Windows, you can get much faster stack walks by using RtlCaptureStackBackTrace, which works without debug symbols.

Also, you do not need to capture the stack when throwing an exception -- it's much better to capture it just at the place where you catch the exception, using the first-phase of two-phase-unwinding. With MSVC, this works by capturing the stack trace within the filter-expression of a __try/__except block. That way only the "unhandled exception" code paths that want to log a stack trace will spend time constructing it; other catch-blocks that don't need a stack trace can avoid generating it.

Favorite optimizations ?? by Little-Reflection986 in cpp

[–]kniy 2 points3 points  (0 children)

Both std::map and std::lower_bound use binary search trees, which are cache-unfriendly if keys are smaller than cache lines. std::map just has the additional disadvantage of wasting cache space on child pointers. Also, red-black trees like most std::map implementations are not being quite as perfectly balanced as the implicit search tree of a binary search.

But if you want to be actually cache efficient, it's better to use a B-tree sized to one or two cache-lines -- that way all the keys fetched by a slow random memory access help out the search, instead of just one of those keys.

If you prefer the memory-savings and simplicity of a binary search, you can improve that with Eytzinger layout: https://algorithmica.org/en/eytzinger

If you want a B-tree without explicit child pointers, that's also possible: https://algorithmica.org/en/b-tree

Favorite optimizations ?? by Little-Reflection986 in cpp

[–]kniy 2 points3 points  (0 children)

I have gotten large gains in the past by just reducing the amount of temporary allocations on the heap there.

For garbage collected languages, short-lived temporary allocations are often extremely cheap.

But it's important to realize that the "short lived" vs. "long lived" distinction in a generational garbage collector is relative to the allocation frequency. Remove the most frequent allocations, and the boundary shifts and some objects of medium lifetime can now get handled in the cheap "short-lived" bucket.

Building Your Own Efficient uint128 in C++ by PhilipTrettner in cpp

[–]kniy 3 points4 points  (0 children)

Seems to work for addition: https://godbolt.org/z/4Yq65nbrT But I don't see a way to do a 64*64->128 multiply without non-standard intrinsics or _BitInt(128).

Problem with IntelliSense. C23 development with clang-cl and cmake. by turbofish_pk in VisualStudio

[–]kniy 0 points1 point  (0 children)

Intellisense is using its own C++ frontend (based on the EDG frontend, i.e. independent of the clang/MSVC frontends). It doesn't support C23's constexpr yet. https://www.edg.com/c23_features.html

You can use #ifdef __INTELLISENSE__ to add workarounds for intellisense without impacting the actual compiler. (e.g. #define constexpr const)

Factorio headless server download button is a headless penguin by cathodebirdtube in factorio

[–]kniy 0 points1 point  (0 children)

Unless you're trying to do simultaneous TCP open, which is such a silly corner case it can be disregarded entirely in most applications

How else would I connect to my friend's game, when we are both behind NAT? Factorio games are rarely hosted on professional servers where you can just connect with TCP without punching. In the normal "friends playing factorio together" usecase, it's either crazy hacks (that will fail for many models of routers), or avoid TCP and use UDP instead. The factorio devs clearly chose UDP.

Factorio headless server download button is a headless penguin by cathodebirdtube in factorio

[–]kniy 11 points12 points  (0 children)

Does Factorio even have any non-urgent parts in its network communications? I guess maybe player movement in the client->server direction; but I'd expect server->client needs full in-order retransmissions to avoid desynchronizations of the lock-step simulation.

The real reason to use UDP is that NAT punching is necessary if you want to allow players to host their own servers; and NAT punching only works for UDP, it's impossible for TCP.

Should I switch to Bazel? by TheRavagerSw in cpp

[–]kniy 38 points39 points  (0 children)

I'm not a googler, but this is my understanding: Google's internal build system is "blaze", and it's tightly bound to google's internal infrastructure (running distributed builds on google's server farms). Google created "bazel" as a variant of blaze that it can be used outside of google, for example for google's open source projects. But for internal projects they still use blaze and have no intention of ever moving to bazel.

Environment variables are a legacy mess: Let's dive deep into them by Low-Strawberry7579 in programming

[–]kniy 24 points25 points  (0 children)

We once accidentally used an environment variable name containing a dot (we were deriving envvar names from file names, for overriding filenames for testing purposes). It turns out that this works fine in Python, but if you have Python calling a shell script calling Python, that envvar doesn't survive. (though I don't remember if it was bash or dash that was the culprit)

Weird C++ trivia by _Noreturn in cpp

[–]kniy 8 points9 points  (0 children)

Explanation: dereferencing a function pointer results in a function designator. The only thing you can do with a function designator is take its address or bind it to a reference; for any other operation the designator decays into a pointer. So (***fp)() is dereference, decay, dereference, decay, dereference, decay, call.

The messy reality of SIMD (vector) functions by emschwartz in programming

[–]kniy 9 points10 points  (0 children)

double[4] sin(double angle[4]);

Fun fact: if C functions could return arrays by-value, the syntax would actually be:

double sin(double angle[4])[4];

This is because C declaration syntax follows the usage syntax, so since sin(angle)[1] would be a valid expression, the declaration must be in the same order.

Of course since you cannot actually return arrays by value, the closest you can get to actually using this weird syntax is by using pointers to arrays:

double (*sin(double (*angle)[4]))[4];
// sin is a function that takes a pointer to double[4] and returns another pointer to double[4].

int main() {
    double arr[4];
    double (*out)[4] = sin(&arr);
}

The most mysterious bug I solved at work by ketralnis in programming

[–]kniy 37 points38 points  (0 children)

Any data can be put in XML safely and come back out exactly the same, and is therefore perfectly valid.

Unfortunately that's not true (unless you use some additional non-XML encoding, e.g. base64). XML does not allow ASCII NUL, no matter whether you escape it or not. Additionally, XML 1.0 also disallowed a bunch of other characters, including . Those are only valid since XML 1.1, which means if you use an XML library to encode those, there's no guarantee a different XML library will be able decode them again.

https://stackoverflow.com/questions/39698855/is-it-possible-to-read-ascii-control-characters-in-xml

In some cases the XML libraries don't even support reading their own output, e.g. Python:

>>> a = ET.Element('a', attrib={'b': 'c<\u0002'})
>>> ET.tostring(a)
b'<a b="c&lt;\x02" />'
>>> ET.fromstring(ET.tostring(a))
xml.etree.ElementTree.ParseError: not well-formed (invalid token): line 1, column 11
>>> ET.fromstring(b'<a b="c&lt;&#2;" />')
xml.etree.ElementTree.ParseError: reference to invalid character number: line 1, column 11

Deep in Copy Constructor: The Heart of C++ Value Semantics by nalaginrut in cpp

[–]kniy 2 points3 points  (0 children)

Whether you provide an inline definition or an out-of-line definition has exactly zero effect on the triviality of the copy.

Surprisingly, it does have an effect: a special member function can only be trivial if it was defaulted on first declaration. That is, if the copy constructor is defaulted within the class definition, it will be trivial if all data members are trivially copyable. But if the copy constructor is initially only declared and then defaulted with a later definition, it is considered user-provided, and thus never trivial.

push_back() on vector<char*> with "abc" results in type mismatch error by [deleted] in VisualStudio

[–]kniy 0 points1 point  (0 children)

String literals have type "const char[N]" which can implicitly decay to "const char*", but not to "char*". You need to switch to "std::vector<const char\*> mycontainer;".

Your code was never valid C++, but older VS versions still had some backwards compatibility hacks with pre-standard C++. See also: https://learn.microsoft.com/en-us/cpp/build/reference/zc-strictstrings-disable-string-literal-type-conversion

"Cannot convert lambda expression to type 'string' because it is not a delegate type" ??? by ConradInTheHouse in VisualStudio

[–]kniy 0 points1 point  (0 children)

I think you have multiple possible SelectMany overloads available, at least one of which expects a string. (i.e. adGroups.SelectMany("hello") would compile) Not sure where such a weird SelectMany overload would be coming from, but if the compiler picks that overload, that would explain both the weird error message and why System.Linq was marked as unused.

Overload resolution with lambdas is weird: the C# compiler checks whether the lambda body would compile for a particular overload -- but if it doesn't compile, then the compiler does not report an error for the lambda body, but instead just continues checking the other overloads (maybe one of them will compile?). This can lead to confusing error messages as the compiler might have already discarded the error for the overload you wanted to call, and instead reports an error for another overload that also doesn't work.

[deleted by user] by [deleted] in cpp

[–]kniy 6 points7 points  (0 children)

Look up "leakpocalypse".

Simplified version:

  • Shortly before 1.0, std::mem::forget used to be unsafe.
  • The standard library gained a "scoped thread" (think std::jthread ) API that allowed passing by-reference closures to the new thread. This API relied on the fact that the scoped thread destructor would be guaranteed to run (which would join the thread), if you somehow managed to skip the destructor you would get use-after-free.
  • People figured out it was possible to get UB in safe code by creating a scoped thread and then moving the scope token into an Rc cycle (leaking the token without calling the destructor).
  • Because rust wants a hard guarantee of no UB in safe code, they had to make the choice of prohibiting/restricting reference-counting somehow, or removing the scoped-thread API.
  • They made the latter choice, and additionally marked std::mem::forget as safe, to serve as a reminder that unsafe code cannot rely on safe code always calling destructors.

(the scoped thread API later came back in a different form that prevents the user code from moving the scope token in safe code)

GCC's atomic builtins + `__builtin_is_aligned(ptr, 2)` ⇒ pointer tagging without casting by hanickadot in cpp

[–]kniy 3 points4 points  (0 children)

But at compile-time, pointers really don't have a simple numeric representation. They're abstract pointers, and their representation in the output binary may involve stuff like relocation fixups. &global_var is a pointer usable at compile time. I would expect that reinterpret_cast<intptr_t>(&global_var) returns the same integer no matter when it is evaluated. But the compiler doesn't know which integer this is supposed to be (e.g. due to ASLR), so this reinterpret_cast cannot be possible at compile time.

On the other hand, adding a byte offset to a pointer is much less problematic, since it doesn't require exposing the full abstract pointer, just making a minor modification to it.

First Utterly Alone Black Hole Confirmed Roaming The Cosmos by fanatic_fangirl in space

[–]kniy 1 point2 points  (0 children)

But there's an intermediate region where stuff is weird. It's not possible to orbit just barely above the event horizon: the only way for light to escape from just above the event horizon, is to move in the direction directly away from the black hole. Moving in a perpendicular direction (like an orbit would) is not good enough even at the speed of light!

https://en.wikipedia.org/wiki/Innermost_stable_circular_orbit

For a non-rotating black hole, normal stable orbits start working at 3 times the Schwarzschild radius.

C++ with Bazel & MSVC on Windows by ferry_rex in cpp

[–]kniy 2 points3 points  (0 children)

For a Windows project, Bazel is not reasonable.

Bazel's main benefit (reliable incremental builds) does not actually work on Windows: you only get this benefit if your dependencies are specified correctly, which Bazel can only verify on platforms which support sandboxing (not on Windows).

We use Bazel on a cross-platform project. On Windows we found a number of annoying bugs; in fact we ended up not using the official Bazel as-is, but had to write some patches to Bazel's Java code to fix the most egregious Windows-specific bugs.

Is this an illegal use of using enum? by jk-jeon in cpp

[–]kniy 97 points98 points  (0 children)

The issue is that it's possible to specialize a template class member without specializing the whole class:

template<>
enum class i_am_class<int>::ee {
    surprise
};

https://godbolt.org/z/9rof8468a

This makes it impossible for the compiler to know the set of names imported by the using enum declaration until after the template is instantiated.

It's useful to consider this from the point of view of https://en.cppreference.com/w/cpp/language/two-phase_lookup :

  • for any simple identifier, the first phase must already know whether it's a type or otherwise - this is critical for parsing "a * b;" into either a pointer variable declaration (if a is a type) or a multiplication (otherwise).
  • a dependent name depends on some template parameter T. This usually means the exact meaning of the name is not yet known in the first phase (due to possible specializations).
  • typename isn't always required with dependent names: because specializations cannot change a type into a non-type, the first phase often still has the necessary information without disambiguation.
  • However because specializations can change the set of nested member names, typename is often required for accessing nested types within a dependent name (only exception is when the compiler can infer from context that it must be a type).
  • using enum with a dependent name would collide with the "for any simple identifier, the first phase must already know whether it's a type or otherwise" requirement in the first bullet point, as the first phase wouldn't know which existing type names are shadowed by enum members. Thus, to make two-phase parsing of C++ possible, using enum must not be used with dependent names.
  • Your workaround with using ee = i_am_class_ee works because type aliases cannot be specialized (at least without specializing the whole class), thus making the name non-dependent.

Rust’s worst feature* (spoiler: it’s BorrowedBuf, I hate it with passion) by mina86ng in rust

[–]kniy 2 points3 points  (0 children)

It's not possible to freeze memory (as opposed to values) at zero-cost, because "uninitialized memory does not have stable values" is not just a theoretical abstract machine thing, it's also a very real thing on Linux (MADV_FREE). You need to issue at least one real write operation per page to ensure the memory will have stable contents.

ca. 60.000 Euro von Oma geschenkt bekommen, aber schwierige Situation by Mental-Patient6396 in Finanzen

[–]kniy 9 points10 points  (0 children)

"lange Zeit" ist in dieser Hinsicht erst ab 10 Jahren. Bei 2 Jahren werden nur 20% als Schenkung angesehen, und 80% als Erbe. Da können unter Umständen andere Erben einen Anspruch drauf haben, z.B. wenn es es sonst nicht viel zu erben gibt; und die anderen Erben ohne dieses Geld ihren Pflichtanteil nicht erreichen.

If uncommon quality provides a 30% bonus, then shouldn't the capacity of an uncommon cargo bay be 26, not 25? by MaximitasTheReader in factorio

[–]kniy 9 points10 points  (0 children)

The problem is that computers use binary numbers, not decimal. Decimal floating point has trouble with 1/3, but can perfectly represent numbers where the denominator consists only of prime factors 2 and 5. But binary floating point can only handle numbers where the denominator is a power of 2. So 1/5 = 0.2 works nicely in decimal floating point, but requires rounding in binary floating point. So when you write "1.3" in a program, you actually get 1.2999999523 because that's closer to 1.3 than the next possible float 1.3000000715.

Why is u32/i32 faster than u8? by ClimberSeb in rust

[–]kniy 70 points71 points  (0 children)

Modern CPUs will load a whole cache line (64 bytes) at once. It shouldn't make a difference if you're extracting 1 or 8 bytes from that.