Showing posts with label c. Show all posts
Showing posts with label c. Show all posts

Mar 3, 2026

[C++] Elaborated type specifier

Reference:
https://en.cppreference.com/w/cpp/language/elaborated_type_specifier.html
https://vsdmars.blogspot.com/2014/02/c11-extended-friend-declaration.html

class T
{
public:
    class U;
private:
    int U;
};
 
int main()
{
    int T;
    T t; // error: the local variable T is found
    class T t; // OK: finds ::T, the local variable T is ignored
    T::U* u; // error: lookup of T::U finds the private data member
    class T::U* u; // OK: the data member is ignored
}

template<typename T>
struct Node
{
    struct Node* Next; // OK: lookup of Node finds the injected-class-name
    struct Data* Data; // OK: declares type Data at global scope
                       // and also declares the data member Data
    friend class ::List; // error: cannot introduce a qualified name
    enum Kind* kind; // error: cannot introduce an enum
};
 
Data* p; // OK: struct Data has been declared

template<typename T>
class Node
{
    friend class T; // error: type parameter cannot appear in an elaborated type specifier;
                    // note that similar declaration `friend T;` is OK.
};
 
class A {};
enum b { f, t };
 
int main()
{
    class A a; // OK: equivalent to 'A a;'
    enum b flag; // OK: equivalent to 'b flag;'
}

enum class E { a, b };
enum E x = E::a; // OK
enum class E y = E::b; // error: 'enum class' cannot introduce an elaborated type specifier
 
struct A {};
class A a; // OK
`

Sep 17, 2022

[elf][note] entry function

ELF executable entry function

_start function facts


_start function implements

  1. Early low-level initialization, such as
    1. Configuring processor registers
    2. Initializing external memory
    3. Enabling caches
    4. Configuring the MMU
  2. Stack initialization, making sure that the stack is properly aligned per the ABI requirements
  3. Frame pointer initialization
  4. Initialization of the C/C++ runtime
    Relocate any relocatable sections (if not handled by the loader or linker)
    1. Initializing global and static memory
    2. Runtime initializes a subset of uninitialized memory (no = in the declaration) to 0.
      This includes global and static variables, but not stack variables. All uninitialized data that needs to be set to 0 is placed into the .bss section of the compiled program image by the linker. The location of the .bss section is identified during initialization, and the memory is typically set to 0 with memset.
    3. C++ global objects must be constructed before calling main. The linker places these constructors into the .init, .init_array, or .ctors section of the image.
      Some compilers also allow C and C++ functions to be marked as a constructor using a compiler attribute (e.g., __attribute__((constuctor))). The constructors are stored in a list by the linker.
      The runtime initialization process iterates through the list and calls each constructor.
    4. Prepare the argc and argv variables for invoking main (even if it’s just setting these to 0/NULL)
    5. Perform any additional setup steps required by the C/C++ standard library implementation.
      These additional runtime initialization steps are run for many programs (but not all):
      1. Heap initialization
      2. Initialize stdio (i.e., stdin, stdout, stderr)
      3. Initialize exception support (if using C++)
      4. Register destructors and other cleanup functions that will run when exiting the program (using atexit and __cxa_atexit)
      5. Assembly files commonly found during this portion of the startup process are crtbegin.s, crtend.s, crti.s, and crtn.s.
      6. Prepare environment variables
  5. Initialization of other scaffolding required by the system
      Program scaffolding setup before main might include:
      1. Threading support and thread local storage
      2. Buffer overrun detection
      3. Stack logging
      4. Run-time error checks
      5. Locale settings
      6. Math error handling
      7. Default math library precision
  6. Jumping to main
  7. Exiting the program with the return code from main

So, how do we get to the _start?

  • Baremetal: Reset Vector
  • Bootloader Launches Application
  • OS Calls an exec function
    Loaders will often perform the following actions:
    • Check permissions
    • Allocate space for the program’s stack
    • Allocate space for the program’s heap
    • Initialize registers (e.g., stack pointer)
    • Push argc, argv, and envp onto the program stack
    • Map virtual address spaces
    • Dynamic linking
    • Relocations
    • Call pre-initialization functions

Feb 24, 2022

[kernnel][C][C++] ternary conditional operator trick in action

Reference:
Return type of '?:' (ternary conditional operator): https://stackoverflow.com/questions/8535226/return-type-of-ternary-conditional-operator

__is_constexpr() macro is dark magic: https://lore.kernel.org/linux-hardening/20220131204357.1133674-1-keescook@chromium.org/?fbclid=IwAR0Rgg_tGDk0qiEyuDsuZwERITSdstxmU2-bOtadb7iOOCtw3tgOPKEQ5hE

Using ternary conditional operator to get the type we want for a template type is often used in C++.

Here the same idea applies in C macro:

#define __is_constexpr(x) \
	(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
Details:
 - sizeof() is an integer constant expression, and does not evaluate the
   value of its operand; it only examines the type of its operand.
 - The results of comparing two integer constant expressions is also
   an integer constant expression.
 - The use of literal "8" is to avoid warnings about unaligned pointers;
   these could otherwise just be "1"s.
 - (long)(x) is used to avoid warnings about 64-bit types on 32-bit
   architectures.
 - The C standard defines an "integer constant expression" as different
   from a "null pointer constant" (an integer constant 0 pointer).
 - The conditional operator ("... ? ... : ...") returns the type of the
   operand that isn't a null pointer constant. This behavior is the
   central mechanism of the macro.
 - If (x) is an integer constant expression, then the "* 0l" resolves it
   into a null pointer constant, which forces the conditional operator
   to return the type of the last operand: "(int *)".
 - If (x) is not an integer constant expression, then the type of the
   conditional operator is from the first operand: "(void *)".
 - sizeof(int) == 4 and sizeof(void) == 1.
 - The ultimate comparison to "sizeof(int)" chooses between either:
     sizeof(*((int *) (8)) == sizeof(int)   (x was a constant expression)
     sizeof(*((void *)(8)) == sizeof(void)  (x was not a constant expression)


For a conditional expression (?:) to be an lvalue, the second and third operands must be lvalues of the same type.
This is because the type and value category of a conditional expression is determined at compile time and must be appropriate whether or not the condition is true.
If one of the operands must be converted to a different type to match the other than the conditional expression cannot be an lvalue as the result of this conversion would not be an lvalue (but a r-value).

Thus:

OK:
int x = 1;
int y = 2;
(x > y ? x : y) = 100; // l-value on the left side of =

Not OK:
int x = 1;
long y = 2;
(x > y ? x : y) = 100; // type conversion to long as r-value type

Mar 25, 2021

[Go][ticket][trick] make 64-bit fields 64-bit aligned on 32-bit systems

Ticket:
cmd/compile: make 64-bit fields 64-bit aligned on 32-bit systems
https://github.com/golang/go/issues/599

Reference:


The idea below is to avoid any padding, thus using 15 bytes, which if using 14 bytes will be used by later 2 bytes short type.
package mylib

import (
	"unsafe"
	"sync/atomic"
)

type Counter struct {
	x [15]byte // instead of "x uint64"
}

func (c *Counter) xAddr() *uint64 {
	// The return must be 8-byte aligned.
	return (*uint64)(unsafe.Pointer(
		uintptr(unsafe.Pointer(&c.x)) + 8 -
		uintptr(unsafe.Pointer(&c.x))%8))
}

func (c *Counter) Add(delta uint64) {
	p := c.xAddr()
	atomic.AddUint64(p, delta)
}

func (c *Counter) Value() uint64 {
	return atomic.LoadUint64(c.xAddr())
}

Jan 7, 2019

[C] static in function parameter

// passing in argument has at least 10 elements.
// C++ doesn't support/need this since we have reference which can't be nullptr.
void bar(int myArray[static 10]);

Jan 1, 2019

[C++][C] The case of forgotten return bug

read: https://yurichev.com/blog/no_return/

Scenarios:
  • Even without return variable from a function signature which returns, %rax is filled with
    something due to compiler will use as less registers as possible.
    Since %rax is filled with something which is not the returning value, this issues a bug
    (in debug/none-optimized mode).
  • While in optimization mode, optimize prevails, thus a function with signature which returns but inside the function body has no return statement, compiler opts out with returning 0, which again, issues a bug.


Take away:
  • Always turns on -Wall during the programming(I'm using Vim) and compiling.
  • In C++17, consider using attribute: [[nodiscard]] a good practice.
    For side-effect only, take Discarded-value expressions into consideration.


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

struct color
{
        int R;
        int G;
        int B;
};

struct color* create_color (int R, int G, int B)
{
        struct color* rt=(struct color*)malloc(sizeof(struct color));
        rt->R=R;
        rt->G=G;
        rt->B=B;
        // must be "return rt;" here
};

int main()
{
        struct color* a=create_color(1,2,3);
        printf ("%d %d %d\n", a->R, a->G, a->B);
};

https://godbolt.org/z/o8bgyD
create_color(int, int, int):
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp
movl %edi, -20(%rbp)
movl %esi, -24(%rbp)
movl %edx, -28(%rbp)
movl $12, %edi
call malloc
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movl -20(%rbp), %edx
movl %edx, (%rax)
movq -8(%rbp), %rax
movl -24(%rbp), %edx
movl %edx, 4(%rax)
movq -8(%rbp), %rax
movl -28(%rbp), %edx
movl %edx, 8(%rax)
nop
leave
ret
.LC0:
.string "%d %d %d\n"
main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl $3, %edx
movl $2, %esi
movl $1, %edi
call create_color(int, int, int)
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movl 8(%rax), %ecx
movq -8(%rbp), %rax
movl 4(%rax), %edx
movq -8(%rbp), %rax
movl (%rax), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
ret

Sep 25, 2018

[Go][c++] padding

[golang]
  • zero length type, i.e, sizeof(T) == 0, consumes 1 byte if it's the last data member inside the struct type.
    If it's _not_ the last data member inside the struct type, it's size remains 0.
    why? it's about taking the zero length type's instance's address.
    We don't want the zero length type instance act as the last data member inside a type refers to other memory location.
    The zero length type instance act as data member inside a type which is not at the last location has 0 length.
  • ref:
    https://dave.cheney.net/2015/10/09/padding-is-hard
    http://www.catb.org/esr/structure-packing/
  • zero length type in namespace's instance's addresses are the same.
    (this is why C++ zero length type's instance always consumes 1 byte for differentiating the instance's address.)
  • https://dave.cheney.net/2014/03/25/the-empty-struct#comment-2815
    * It's not true that "a value must be aligned in memory to a multiple of its width."
    * Each type has another property, its alignment. Alignments are always powers of two.
    * The alignment of a basic type is usually equal to its width, but the alignment of a struct is the maximum alignment of any field, and the alignment of an array is the alignment of the array element.
    * The maximum alignment of any value is therefore the maximum alignment of any basic type.
    * Even on 32-bit systems this is often 8 bytes, because atomic operations on 64-bit values typically require 64-bit alignment.
    * To be concrete, a struct containing 3 int32 fields has alignment 4 but width 12.
    * It is true that a value's width is always a multiple of its alignment.
    * One implication is that there is no padding between array elements.
    package main
    
    import "unsafe"
    
    type Fun struct {
    	i  *int
    	bl bool
    	b  byte
    }
    
    func main() {
    
    	a := [2]Fun{Fun{}, Fun{}}
    	println(unsafe.Sizeof(Fun{})) // 16
    	println(unsafe.Sizeof(a))     // 32
    
    	b := [2]*int{nil, nil}
    	println(unsafe.Sizeof(b)) // 16
    }
    
  • https://golang.org/ref/spec#Size_and_alignment_guarantees
    * A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero.
    * Two distinct zero-size variables may have the same address in memory. 
  • runtime.zerobase https://github.com/golang/go/blob/383b447e0da5bd1fcdc2439230b5a1d3e3402117/src/runtime/malloc.go#L813
    All zero sized instance has this same address at run-time.
    Found through cmd:
    $ go tool nm
package main

import (
 "fmt"
 _ "unsafe"
)

//go:linkname zerobase runtime.zerobase
var zerobase uintptr

func main() {
 var s struct{}
 var a [42]struct{}

 fmt.Printf("zerobase = %p\n", &zerobase)
 fmt.Printf("       s = %p\n", &s)
 fmt.Printf("       a = %p\n", &a)
}

[C++]

Jan 14, 2018

[clang] __builtin_unreachable

https://clang.llvm.org/docs/LanguageExtensions.html

__builtin_unreachable

__builtin_unreachable is used to indicate that a specific point in the program cannot be reached, even if the compiler might otherwise think it can. This is useful to improve optimization and eliminates certain warnings. For example, without the __builtin_unreachable in the example below, the compiler assumes that the inline asm can fall through and prints a “function declared ‘noreturn’ should not return” warning.
Syntax:
__builtin_unreachable()
Example of use:
void myabort(void) __attribute__((noreturn));
void myabort(void) {
  asm("int3");
  __builtin_unreachable();
}
Description:
The __builtin_unreachable() builtin has completely undefined behavior. Since it has undefined behavior, it is a statement that it is never reached and the optimizer can take advantage of this to produce better code. This builtin takes no arguments and produces a void result.
Query for this feature with __has_builtin(__builtin_unreachable).

Jan 23, 2016

[Linus] Memory barriers discuss

Linus: memory_barriers discuss


The "barrier()" macro is there entirely to defeat the compiler reordering
that is sometimes deadly.

For example, the compiler doesn't know _squat_ about locking, and that's
just as well, since compiler-generated locking tends to suck dead donkeys
through a straw (ie monitors etc that some languages have, and that are
tied to the data structures and tend to be overly careful since the
compiler doesn't actually understand what's going on).

But not knowing about locking means that if you have code like this:

        local_cpu_lock = 1;

        .. do something critical ..

        local_cpu_lock = 0;

and you use the "local_cpu_lock" to tell interrupts to not do certain
things, the compiler will be totally clueless.

[likely or unlikely] a easy misleading.

Reference:
Using likely() and unlikely()
Clang ignores branch predictor hints using __builtin_expect
#define likely(x) __builtin_expect ((x), 1)
#define unlikely(x) __builtin_expect ((x), 0)

The rule of the thumb is: Mark branch that you want to be executed quickly as "likely" and the other branch as "unlikely".
#ifdef FOO
#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)
#else
#define likely(x)       x
#define unlikely(x)     x
#endif
 
volatile int x,y,z;
int array[100];
 
// switch like
char const* b(int e) {
    if (likely(e == 0))
    {
        // for(int i=0; i<100;i++)
        //     array[i]=x;    
        return "0";
    }    
    else if (e == 1)
    {
        for(int i=0; i<100;i++)
            array[i]=y;
        return "1";
    }
    else
    {
        for(int i=0; i<100;i++)
            array[i]=z;
        return "f";
    }
}

Jan 4, 2015

[C++] Tail Call op Articles

LLVM Tail call optimization
stackoverflow : LLVM tail call optimization
http://en.wikipedia.org/wiki/Tail_call

Tail call optimization, callee reusing the stack of the caller, is currently supported on x86/x86-64 and PowerPC, and AArch64.

It is performed if:

  • Caller and callee have the calling convention fastcc, cc 10 (GHC calling convention) or cc 11 (HiPE calling convention). 
  • The call is a tail call - in tail position (ret immediately follows call and ret uses value of call or is void). 
  • Option -tailcallopt is enabled. 
  • Platform-specific constraints are met. 
  • x86/x86-64 constraints: 
    • No variable argument lists are used. 
    • On x86-64 when generating GOT/PIC code only module-local calls (visibility = hidden or protected) are supported.
  • AArch64 constraints: 
    • No variable argument lists are used.

Nov 12, 2014

[C++] tail call

Reference:
Tackling C++ Tail Calls

Tail Call Optimization (TCO), dependency, broken debug builds in C and C++ — and gcc 4.8

http://en.wikipedia.org/wiki/Tail_call

[c++] Cache Member Variables

Reference:

Accessing member variables is a common operation in C++ member functions.

The compiler must often load member variables from memory through the this pointer. Because values are being loaded through a pointer, the compiler sometimes cannot determine when a second load must be performed or whether the value loaded before is still valid. 

In these cases, the compiler must choose the safe, but slow, approach and reload the member variable each time it is accessed. 

You can avoid unnecessary memory reloads by explicitly caching the values of member variables in local variables, as follows: 
  • Declare a local variable and initialize it with the value of the member variable. 
  • Use the local variable in place of the member variable throughout the function. 
  • If the local variable changes, assign the final value of the local variable to the member variable. 
However, this optimization may yield undesired results if the member function calls another member function on that object.

This optimization is most productive when the values can reside in registers, as is the case with primitive types. 

The optimization may also be productive for memory-based values because the reduced aliasing gives the compiler more opportunity to optimize. 

This optimization may be counter productive if the member variable is often passed by reference, either explicitly or implicitly.

Oct 23, 2014

[C][C++] difference between char array[] and char *array, why char [] not char* could be used in non-type argument for template.

Reference:

quote:
A string literal is a literal with array type, and in C there is no way for an array type to exist in an expression except as an lvalue. 
String literals could have been specified to have pointer type (rather than array type that usually decays to a pointer) pointing to the string "contents", but this would make them rather less useful; in particular, the sizeof operator could not be applied to them. 

Note that C99 introduced compound literals, which are also lvalues, so having a literal be an lvalue is no longer a special exception; it's closer to being the norm.

quote:

const char hello[] = {'h', 'e', 'l', 'l', 'o', '\0'};

This creates an array of 6 bytes in writable memory (on the stack if this is inside a function, in a data segment if directly at global scope or inside a namespace), to be initialized with the ASCII codes for each of those characters in turn.
i.e 以上可以被take address, 可以用於C++ template non-type parameter.

char *hello = "hello";

此為runtime(其所在記憶體位置由loader決定), 不能被take address at compile time, 故不能用於template non-type parameter.

"hello" is a string literal,
which typically means: the OS loader code that loads your program into memory and starts it running will have copied the text "hello\0" from your executable image into some memory that will then have been set to be read only, and a separate variable named "hello" - which is of whatever size pointers are in your program (e.g. 4 bytes for 32-bit applications, 8 for 64-bit) - will exist on the stack (if the line above appears inside a function) or in writable memory segment (if the line is at global or namespace scope), and the address of the former textual data will be copied into the hello pointer. you can change hello to point somewhere else (e.g. to another language's equivalent text), but normally shouldn't try to change the string literal to which the above code points hello.

Beware not to modify char*, which should be const char*, since we could cache the char string with same value at the beginning.