Is std::array (from the C++ STL) safe to use on arduino? Does it use dynamic memory allocation?
std::array does not use dynamic memory allocations to store its elements. If you use static or stack-allocated std::arrays, you don't have to worry about memory fragmentation (contrary to std::vector or String, for example).
Of course, if you allocate arrays on the stack, you can have a stack overflow if you call too many functions, either by design, or because of a bug.

That brings us to the problem of exceptions. If you look in the standard, you'll see that most of the member functions of std::array are marked noexcept, with the notable exception of the element accessors. Obviously, the at() accessor can throw, but you'd expect that operator[] doesn't.
In practice, operator[] is marked noexcept in the version of the STL (GCC9) that I have on my computer, so if you check it in the STL implementation you're using, you can be pretty confident that it won't throw. Just keep in mind that this is not mandated by the standard.

/usr/include/c++/9/array

      // Element access.
      _GLIBCXX17_CONSTEXPR reference
      operator[](size_type __n) noexcept
      { return _AT_Type::_S_ref(_M_elems, __n); }

      constexpr const_reference
      operator[](size_type __n) const noexcept
      { return _AT_Type::_S_ref(_M_elems, __n); }


The debug version is also marked noexcept, but it can throw/abort if the index is out of bounds:

/usr/include/c++/9/debug/array

      // Element access.
      _GLIBCXX17_CONSTEXPR reference
      operator[](size_type __n) noexcept
      {
    __glibcxx_check_subscript(__n);
    return _AT_Type::_S_ref(_M_elems, __n);
      }

      constexpr const_reference
      operator[](size_type __n) const noexcept
      {
    return __n &lt; _Nm ? _AT_Type::_S_ref(_M_elems, __n)
     : (_GLIBCXX_THROW_OR_ABORT(_Array_check_subscript&lt;_Nm&gt;(__n)),
        _AT_Type::_S_ref(_M_elems, 0));
      }


The STL implementations for Arduino I found online don't seem to have the &lt;array&gt; header.

Luckily, you can easily create your own array container, for example:

template &lt;class T, size_t N&gt;
struct Array {
    // Storage
    T data[N];

    static size_t length() { return N; }
    using type = T;

    // Item access
    T &amp;operator[](size_t index) { return data[index]; }
    const T &amp;operator[](size_t index) const { return data[index]; }

    // Iterators
    T *begin() { return &amp;data[0]; }
    const T *begin() const { return &amp;data[0]; }
    T *end() { return &amp;data[N]; }
    const T *end() const { return &amp;data[N]; }

    // Comparisons
    bool operator==(const Array&lt;T, N&gt; &amp;rhs) const {
        if (this == &amp;rhs)
            return true;
        for (size_t i = 0; i &lt; N; i++)
            if ((*this)[i] != rhs[i])
                return false;
        return true;
    }
    bool operator!=(const Array&lt;T, N&gt; &amp;rhs) const {
        return !(*this == rhs);
    }
};

void setup() {
  Serial.begin(115200);
  while (!Serial);

  // Create an array
  Array&lt;int, 5&gt; myArray = {1, 2, 3, 4, 5};
  // On older compilers, use double braces:
  //    Array&lt;int, 5&gt; myArray = {{1, 2, 3, 4, 5}};

  // Iterate over the array
  for (int element : myArray)
    Serial.println(element);

  // Access elements of the array
  myArray[2] = 30;
  Serial.println(myArray[2]);
}

void loop() {}